December 25, 2023


Handle promises inside useEffect

Context

Init pdf inside useEffect leads to error when component re render. This happens with react strict mode because useEffect runs twice.

Screenshot 2023-12-25 at 3.28.13 PM.png

useEffect(() => {
	initPdf(pageDetail)
}, [pageDetail])

Solutions

  1. Beginner react development

    Idea: Cancel all promise except the first one

    How: Create a flag using useRef, change the flag when useEffect is run for the first time and prevent it from running again

    const isLoaded = useRef<boolean>(false)
    
    useEffect(() => {
    	****if(!isLoaded.current) {
    		return
    	}
    
    	initPdf(pageDetail)
    	isLoaded.current = true
    }, [pageDetail])
    
  2. Normal react development

    Idea: Cancel all promise except the last one

    How: Move init pdf function to a set timeout callback then cancel the callback in useEffect clean up

    Explain: In react, useEffect will only run after the useEffect before it cleans up. By using timeout, we ensure that promise won’t run if the same useEffect is called multiple time. Hence, since the last useEffect is the only one that affecting the UI, we don’t really need to worry about the others

    Screenshot 2023-12-25 at 3.40.42 PM.png

    useEffect(() => {
    	****const timeout = setTimeout(() => initPdf(pageDetail), 1000)
    
    	return () => {
    		clearTimeout(timeout)
    	}
    }, [pageDetail])
    
  3. Advanced react development

    Idea: Run all promises by stacking them

    How: Save the promises in a ref and run it once the above finish

    Explain: This ensure all promises are run and we don’t miss out on any update

    const ref = useRef<Promise<void> | null>(null)
    
    useEffect(() => {
    	if (ref.current === null) {
    		ref.current = initPdf(pageDetail)
    	}
      else {
        ref.current.then(() => ref.current = initPdf(pageDetail))
    	}
    }, [pageDetail])
    
  4. Super ultra react development

    Idea: Cancel the current promise when interrupt

    How: Use 2 flags, one to track if promise is interrupted and one to store the current promise. On useEffect clean up, we will set interrupt to true and cancel the current promise

    **const currentTask = useRef<any>(null)
    const isInterrupted = useRef<boolean>(false)**
    
    useEffect(() => {
      async function initPdf() {
        if (pageDetail === undefined)
          return
    
        const url = pageDetail.signedURL
    
        **if (isInterrupted.current)
          return
        const pdfTask = getDocument({ url })
        currentTask.current = async () => await pdfTask.destroy()
        const pdf = await pdfTask.promise**
    
        const page = await pdf.getPage(1)
        const viewport = page.getViewport({ scale: SCALE })
    
        const canvas = canvasRef.current
        if (!canvas)
          return
    
        const canvasContext = canvas.getContext('2d')
        if (!canvasContext)
          throw new Error('Incorrect canvas context')
    
        canvas.height = viewport.height
        canvas.width = viewport.width
    
        const renderContext = { canvasContext, viewport }
    
        **if (isInterrupted.current)
          return
        const renderTask = page.render(renderContext)
        currentTask.current = () => renderTask.cancel()
        await renderTask.promise**
    
        await pdf.cleanup()
        await pdf.destroy()
      }
    
    	**isInterrupted.current = false**
      initPdf()
    
      **return () => {
        isInterrupted.current = true
        currentTask.current?.()
      }**
    }, [pageDetail])
    

December 20, 2023


Create a new repo from an existing repo

Step 1. Create new empty repo

Step 2. Go to the old repo