Init pdf inside useEffect leads to error when component re render. This happens with react strict mode because useEffect runs twice.
initPDF
useEffect(() => {
initPdf(pageDetail)
}, [pageDetail])
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])
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
useEffect(() => {
****const timeout = setTimeout(() => initPdf(pageDetail), 1000)
return () => {
clearTimeout(timeout)
}
}, [pageDetail])
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])
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])
Step 1. Create new empty repo
Step 2. Go to the old repo