From ce6db0341fe1a43a89ca6915421526133c7f289b Mon Sep 17 00:00:00 2001 From: Wojciech Maj Date: Tue, 11 Jun 2024 01:00:47 +0200 Subject: [PATCH] Define effects in useEffect call Workaround for https://github.com/biomejs/biome/issues/3080 --- packages/react-pdf/src/Document.tsx | 102 +++++----- packages/react-pdf/src/Outline.tsx | 57 +++--- packages/react-pdf/src/Page.tsx | 80 ++++---- .../react-pdf/src/Page/AnnotationLayer.tsx | 172 ++++++++-------- packages/react-pdf/src/Page/Canvas.tsx | 93 ++++----- packages/react-pdf/src/Page/TextLayer.tsx | 187 +++++++++--------- packages/react-pdf/src/StructTree.tsx | 67 +++---- test/CustomRenderer.tsx | 60 +++--- 8 files changed, 410 insertions(+), 408 deletions(-) diff --git a/packages/react-pdf/src/Document.tsx b/packages/react-pdf/src/Document.tsx index 8bffdb1bc..bac901484 100644 --- a/packages/react-pdf/src/Document.tsx +++ b/packages/react-pdf/src/Document.tsx @@ -485,51 +485,53 @@ const Document = forwardRef(function Document( } } - function resetDocument() { - pdfDispatch({ type: 'RESET' }); - } - - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(resetDocument, [pdfDispatch, source]); - - function loadDocument() { - if (!source) { - return; - } - - const documentInitParams: Source = { - ...source, - ...options, - }; - - const destroyable = pdfjs.getDocument(documentInitParams); - if (onLoadProgress) { - destroyable.onProgress = onLoadProgress; - } - if (onPassword) { - destroyable.onPassword = onPassword; - } - const loadingTask = destroyable; - - loadingTask.promise - .then((nextPdf) => { - pdfDispatch({ type: 'RESOLVE', value: nextPdf }); - }) - .catch((error) => { - if (loadingTask.destroyed) { - return; - } + // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on source change + useEffect( + function resetDocument() { + pdfDispatch({ type: 'RESET' }); + }, + [pdfDispatch, source], + ); - pdfDispatch({ type: 'REJECT', error }); - }); + // biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change + useEffect( + function loadDocument() { + if (!source) { + return; + } - return () => { - loadingTask.destroy(); - }; - } + const documentInitParams: Source = { + ...source, + ...options, + }; - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(loadDocument, [options, pdfDispatch, source]); + const destroyable = pdfjs.getDocument(documentInitParams); + if (onLoadProgress) { + destroyable.onProgress = onLoadProgress; + } + if (onPassword) { + destroyable.onPassword = onPassword; + } + const loadingTask = destroyable; + + loadingTask.promise + .then((nextPdf) => { + pdfDispatch({ type: 'RESOLVE', value: nextPdf }); + }) + .catch((error) => { + if (loadingTask.destroyed) { + return; + } + + pdfDispatch({ type: 'REJECT', error }); + }); + + return () => { + loadingTask.destroy(); + }; + }, + [options, pdfDispatch, source], + ); // biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change useEffect(() => { @@ -545,14 +547,14 @@ const Document = forwardRef(function Document( onLoadSuccess(); }, [pdf]); - function setupLinkService() { - linkService.current.setViewer(viewer.current); - linkService.current.setExternalLinkRel(externalLinkRel); - linkService.current.setExternalLinkTarget(externalLinkTarget); - } - - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(setupLinkService, [externalLinkRel, externalLinkTarget]); + useEffect( + function setupLinkService() { + linkService.current.setViewer(viewer.current); + linkService.current.setExternalLinkRel(externalLinkRel); + linkService.current.setExternalLinkTarget(externalLinkTarget); + }, + [externalLinkRel, externalLinkTarget], + ); const registerPage = useCallback((pageIndex: number, ref: HTMLDivElement) => { pages.current[pageIndex] = ref; diff --git a/packages/react-pdf/src/Outline.tsx b/packages/react-pdf/src/Outline.tsx index 118bdf6b5..5f7fcca3c 100644 --- a/packages/react-pdf/src/Outline.tsx +++ b/packages/react-pdf/src/Outline.tsx @@ -115,35 +115,36 @@ export default function Outline(props: OutlineProps) { } } - function resetOutline() { - outlineDispatch({ type: 'RESET' }); - } - - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(resetOutline, [outlineDispatch, pdf]); - - function loadOutline() { - if (!pdf) { - // Impossible, but TypeScript doesn't know that - return; - } - - const cancellable = makeCancellable(pdf.getOutline()); - const runningTask = cancellable; - - cancellable.promise - .then((nextOutline) => { - outlineDispatch({ type: 'RESOLVE', value: nextOutline }); - }) - .catch((error) => { - outlineDispatch({ type: 'REJECT', error }); - }); - - return () => cancelRunningTask(runningTask); - } + // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on pdf change + useEffect( + function resetOutline() { + outlineDispatch({ type: 'RESET' }); + }, + [outlineDispatch, pdf], + ); - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(loadOutline, [outlineDispatch, pdf]); + useEffect( + function loadOutline() { + if (!pdf) { + // Impossible, but TypeScript doesn't know that + return; + } + + const cancellable = makeCancellable(pdf.getOutline()); + const runningTask = cancellable; + + cancellable.promise + .then((nextOutline) => { + outlineDispatch({ type: 'RESOLVE', value: nextOutline }); + }) + .catch((error) => { + outlineDispatch({ type: 'REJECT', error }); + }); + + return () => cancelRunningTask(runningTask); + }, + [outlineDispatch, pdf], + ); // biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change useEffect(() => { diff --git a/packages/react-pdf/src/Page.tsx b/packages/react-pdf/src/Page.tsx index 3c796d606..79bfd7317 100644 --- a/packages/react-pdf/src/Page.tsx +++ b/packages/react-pdf/src/Page.tsx @@ -387,21 +387,22 @@ export default function Page(props: PageProps) { return scaleWithDefault * pageScale; }, [height, page, rotate, scaleProps, width]); - function hook() { - return () => { - if (!isProvided(pageIndex)) { - // Impossible, but TypeScript doesn't know that - return; - } - - if (_enableRegisterUnregisterPage && unregisterPage) { - unregisterPage(pageIndex); - } - }; - } - - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(hook, [_enableRegisterUnregisterPage, pdf, pageIndex, unregisterPage]); + // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on pdf change + useEffect( + function hook() { + return () => { + if (!isProvided(pageIndex)) { + // Impossible, but TypeScript doesn't know that + return; + } + + if (_enableRegisterUnregisterPage && unregisterPage) { + unregisterPage(pageIndex); + } + }; + }, + [_enableRegisterUnregisterPage, pdf, pageIndex, unregisterPage], + ); /** * Called when a page is loaded successfully @@ -442,34 +443,35 @@ export default function Page(props: PageProps) { } } - function resetPage() { - pageDispatch({ type: 'RESET' }); - } - - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(resetPage, [pageDispatch, pdf, pageIndex]); - - function loadPage() { - if (!pdf || !pageNumber) { - return; - } + // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on pdf and pageIndex change + useEffect( + function resetPage() { + pageDispatch({ type: 'RESET' }); + }, + [pageDispatch, pdf, pageIndex], + ); - const cancellable = makeCancellable(pdf.getPage(pageNumber)); - const runningTask = cancellable; + useEffect( + function loadPage() { + if (!pdf || !pageNumber) { + return; + } - cancellable.promise - .then((nextPage) => { - pageDispatch({ type: 'RESOLVE', value: nextPage }); - }) - .catch((error) => { - pageDispatch({ type: 'REJECT', error }); - }); + const cancellable = makeCancellable(pdf.getPage(pageNumber)); + const runningTask = cancellable; - return () => cancelRunningTask(runningTask); - } + cancellable.promise + .then((nextPage) => { + pageDispatch({ type: 'RESOLVE', value: nextPage }); + }) + .catch((error) => { + pageDispatch({ type: 'REJECT', error }); + }); - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(loadPage, [pageDispatch, pdf, pageNumber]); + return () => cancelRunningTask(runningTask); + }, + [pageDispatch, pdf, pageNumber], + ); // biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change useEffect(() => { diff --git a/packages/react-pdf/src/Page/AnnotationLayer.tsx b/packages/react-pdf/src/Page/AnnotationLayer.tsx index 427404296..dd665c394 100644 --- a/packages/react-pdf/src/Page/AnnotationLayer.tsx +++ b/packages/react-pdf/src/Page/AnnotationLayer.tsx @@ -78,36 +78,37 @@ export default function AnnotationLayer() { } } - function resetAnnotations() { - annotationsDispatch({ type: 'RESET' }); - } - - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(resetAnnotations, [annotationsDispatch, page]); - - function loadAnnotations() { - if (!page) { - return; - } - - const cancellable = makeCancellable(page.getAnnotations()); - const runningTask = cancellable; - - cancellable.promise - .then((nextAnnotations) => { - annotationsDispatch({ type: 'RESOLVE', value: nextAnnotations }); - }) - .catch((error) => { - annotationsDispatch({ type: 'REJECT', error }); - }); - - return () => { - cancelRunningTask(runningTask); - }; - } + // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on page change + useEffect( + function resetAnnotations() { + annotationsDispatch({ type: 'RESET' }); + }, + [annotationsDispatch, page], + ); - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(loadAnnotations, [annotationsDispatch, page]); + useEffect( + function loadAnnotations() { + if (!page) { + return; + } + + const cancellable = makeCancellable(page.getAnnotations()); + const runningTask = cancellable; + + cancellable.promise + .then((nextAnnotations) => { + annotationsDispatch({ type: 'RESOLVE', value: nextAnnotations }); + }) + .catch((error) => { + annotationsDispatch({ type: 'REJECT', error }); + }); + + return () => { + cancelRunningTask(runningTask); + }; + }, + [annotationsDispatch, page], + ); // biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change useEffect(() => { @@ -142,66 +143,59 @@ export default function AnnotationLayer() { [page, rotate, scale], ); - function renderAnnotationLayer() { - if (!pdf || !page || !linkService || !annotations) { - return; - } - - const { current: layer } = layerElement; - - if (!layer) { - return; - } - - const clonedViewport = viewport.clone({ dontFlip: true }); - - const annotationLayerParameters = { - accessibilityManager: null, // TODO: Implement this - annotationCanvasMap: null, // TODO: Implement this - annotationEditorUIManager: null, // TODO: Implement this - div: layer, - l10n: null, // TODO: Implement this - page, - viewport: clonedViewport, - }; - - const renderParameters = { - annotations, - annotationStorage: pdf.annotationStorage, - div: layer, - imageResourcesPath, - linkService, - page, - renderForms, - viewport: clonedViewport, - }; - - layer.innerHTML = ''; - - try { - new pdfjs.AnnotationLayer(annotationLayerParameters).render(renderParameters); - - // Intentional immediate callback - onRenderSuccess(); - } catch (error) { - onRenderError(error); - } - - return () => { - // TODO: Cancel running task? - }; - } - - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(renderAnnotationLayer, [ - annotations, - imageResourcesPath, - linkService, - page, - pdf, - renderForms, - viewport, - ]); + // biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change + useEffect( + function renderAnnotationLayer() { + if (!pdf || !page || !linkService || !annotations) { + return; + } + + const { current: layer } = layerElement; + + if (!layer) { + return; + } + + const clonedViewport = viewport.clone({ dontFlip: true }); + + const annotationLayerParameters = { + accessibilityManager: null, // TODO: Implement this + annotationCanvasMap: null, // TODO: Implement this + annotationEditorUIManager: null, // TODO: Implement this + div: layer, + l10n: null, // TODO: Implement this + page, + viewport: clonedViewport, + }; + + const renderParameters = { + annotations, + annotationStorage: pdf.annotationStorage, + div: layer, + imageResourcesPath, + linkService, + page, + renderForms, + viewport: clonedViewport, + }; + + layer.innerHTML = ''; + + try { + new pdfjs.AnnotationLayer(annotationLayerParameters).render(renderParameters); + + // Intentional immediate callback + onRenderSuccess(); + } catch (error) { + onRenderError(error); + } + + return () => { + // TODO: Cancel running task? + }; + }, + [annotations, imageResourcesPath, linkService, page, pdf, renderForms, viewport], + ); return (
diff --git a/packages/react-pdf/src/Page/Canvas.tsx b/packages/react-pdf/src/Page/Canvas.tsx index adbab74c2..6e4652d59 100644 --- a/packages/react-pdf/src/Page/Canvas.tsx +++ b/packages/react-pdf/src/Page/Canvas.tsx @@ -87,52 +87,53 @@ export default function Canvas(props: CanvasProps) { [page, rotate, scale], ); - function drawPageOnCanvas() { - if (!page) { - return; - } - - // Ensures the canvas will be re-rendered from scratch. Otherwise all form data will stay. - page.cleanup(); - - const { current: canvas } = canvasElement; - - if (!canvas) { - return; - } - - canvas.width = renderViewport.width; - canvas.height = renderViewport.height; - - canvas.style.width = `${Math.floor(viewport.width)}px`; - canvas.style.height = `${Math.floor(viewport.height)}px`; - canvas.style.visibility = 'hidden'; - - const renderContext: RenderParameters = { - annotationMode: renderForms ? ANNOTATION_MODE.ENABLE_FORMS : ANNOTATION_MODE.ENABLE, - canvasContext: canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D, - viewport: renderViewport, - }; - if (canvasBackground) { - renderContext.background = canvasBackground; - } - - const cancellable = page.render(renderContext); - const runningTask = cancellable; - - cancellable.promise - .then(() => { - canvas.style.visibility = ''; - - onRenderSuccess(); - }) - .catch(onRenderError); - - return () => cancelRunningTask(runningTask); - } - - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(drawPageOnCanvas, [canvasBackground, page, renderForms, renderViewport, viewport]); + // biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change + useEffect( + function drawPageOnCanvas() { + if (!page) { + return; + } + + // Ensures the canvas will be re-rendered from scratch. Otherwise all form data will stay. + page.cleanup(); + + const { current: canvas } = canvasElement; + + if (!canvas) { + return; + } + + canvas.width = renderViewport.width; + canvas.height = renderViewport.height; + + canvas.style.width = `${Math.floor(viewport.width)}px`; + canvas.style.height = `${Math.floor(viewport.height)}px`; + canvas.style.visibility = 'hidden'; + + const renderContext: RenderParameters = { + annotationMode: renderForms ? ANNOTATION_MODE.ENABLE_FORMS : ANNOTATION_MODE.ENABLE, + canvasContext: canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D, + viewport: renderViewport, + }; + if (canvasBackground) { + renderContext.background = canvasBackground; + } + + const cancellable = page.render(renderContext); + const runningTask = cancellable; + + cancellable.promise + .then(() => { + canvas.style.visibility = ''; + + onRenderSuccess(); + }) + .catch(onRenderError); + + return () => cancelRunningTask(runningTask); + }, + [canvasBackground, page, renderForms, renderViewport, viewport], + ); const cleanup = useCallback(() => { const { current: canvas } = canvasElement; diff --git a/packages/react-pdf/src/Page/TextLayer.tsx b/packages/react-pdf/src/Page/TextLayer.tsx index 8e22e450a..dee9d2135 100644 --- a/packages/react-pdf/src/Page/TextLayer.tsx +++ b/packages/react-pdf/src/Page/TextLayer.tsx @@ -80,34 +80,35 @@ export default function TextLayer() { } } - function resetTextContent() { - textContentDispatch({ type: 'RESET' }); - } - - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(resetTextContent, [page, textContentDispatch]); - - function loadTextContent() { - if (!page) { - return; - } + // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on page change + useEffect( + function resetTextContent() { + textContentDispatch({ type: 'RESET' }); + }, + [page, textContentDispatch], + ); - const cancellable = makeCancellable(page.getTextContent()); - const runningTask = cancellable; + useEffect( + function loadTextContent() { + if (!page) { + return; + } - cancellable.promise - .then((nextTextContent) => { - textContentDispatch({ type: 'RESOLVE', value: nextTextContent }); - }) - .catch((error) => { - textContentDispatch({ type: 'REJECT', error }); - }); + const cancellable = makeCancellable(page.getTextContent()); + const runningTask = cancellable; - return () => cancelRunningTask(runningTask); - } + cancellable.promise + .then((nextTextContent) => { + textContentDispatch({ type: 'RESOLVE', value: nextTextContent }); + }) + .catch((error) => { + textContentDispatch({ type: 'REJECT', error }); + }); - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(loadTextContent, [page, textContentDispatch]); + return () => cancelRunningTask(runningTask); + }, + [page, textContentDispatch], + ); // biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change useEffect(() => { @@ -171,84 +172,84 @@ export default function TextLayer() { [page, rotate, scale], ); - function renderTextLayer() { - if (!page || !textContent) { - return; - } + useLayoutEffect( + function renderTextLayer() { + if (!page || !textContent) { + return; + } - const { current: layer } = layerElement; + const { current: layer } = layerElement; - if (!layer) { - return; - } + if (!layer) { + return; + } - layer.innerHTML = ''; + layer.innerHTML = ''; - const textContentSource = page.streamTextContent({ includeMarkedContent: true }); + const textContentSource = page.streamTextContent({ includeMarkedContent: true }); - const parameters = { - container: layer, - textContentSource, - viewport, - }; - - const cancellable = new pdfjs.TextLayer(parameters); - const runningTask = cancellable; - - cancellable - .render() - .then(() => { - const end = document.createElement('div'); - end.className = 'endOfContent'; - layer.append(end); - endElement.current = end; - - const layerChildren = layer.querySelectorAll('[role="presentation"]'); - - if (customTextRenderer) { - let index = 0; - textContent.items.forEach((item, itemIndex) => { - if (!isTextItem(item)) { - return; - } - - const child = layerChildren[index]; - - if (!child) { - return; - } - - const content = customTextRenderer({ - pageIndex, - pageNumber, - itemIndex, - ...item, - }); + const parameters = { + container: layer, + textContentSource, + viewport, + }; - child.innerHTML = content; - index += item.str && item.hasEOL ? 2 : 1; - }); - } + const cancellable = new pdfjs.TextLayer(parameters); + const runningTask = cancellable; - // Intentional immediate callback - onRenderSuccess(); - }) - .catch(onRenderError); + cancellable + .render() + .then(() => { + const end = document.createElement('div'); + end.className = 'endOfContent'; + layer.append(end); + endElement.current = end; - return () => cancelRunningTask(runningTask); - } + const layerChildren = layer.querySelectorAll('[role="presentation"]'); - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useLayoutEffect(renderTextLayer, [ - customTextRenderer, - onRenderError, - onRenderSuccess, - page, - pageIndex, - pageNumber, - textContent, - viewport, - ]); + if (customTextRenderer) { + let index = 0; + textContent.items.forEach((item, itemIndex) => { + if (!isTextItem(item)) { + return; + } + + const child = layerChildren[index]; + + if (!child) { + return; + } + + const content = customTextRenderer({ + pageIndex, + pageNumber, + itemIndex, + ...item, + }); + + child.innerHTML = content; + index += item.str && item.hasEOL ? 2 : 1; + }); + } + + // Intentional immediate callback + onRenderSuccess(); + }) + .catch(onRenderError); + + return () => cancelRunningTask(runningTask); + }, + [ + customTextRenderer, + onRenderError, + onRenderSuccess, + page, + pageIndex, + pageNumber, + textContent, + viewport, + ], + ); return (
{ - structTreeDispatch({ type: 'RESOLVE', value: nextStructTree }); - }) - .catch((error) => { - structTreeDispatch({ type: 'REJECT', error }); - }); - - return () => cancelRunningTask(runningTask); - } - - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(loadStructTree, [customTextRenderer, page, structTreeDispatch]); + // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on page change + useEffect( + function resetStructTree() { + structTreeDispatch({ type: 'RESET' }); + }, + [structTreeDispatch, page], + ); + + useEffect( + function loadStructTree() { + if (customTextRenderer) { + // TODO: Document why this is necessary + return; + } + + if (!page) { + return; + } + + const cancellable = makeCancellable(page.getStructTree()); + const runningTask = cancellable; + + cancellable.promise + .then((nextStructTree) => { + structTreeDispatch({ type: 'RESOLVE', value: nextStructTree }); + }) + .catch((error) => { + structTreeDispatch({ type: 'REJECT', error }); + }); + + return () => cancelRunningTask(runningTask); + }, + [customTextRenderer, page, structTreeDispatch], + ); // biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change useEffect(() => { diff --git a/test/CustomRenderer.tsx b/test/CustomRenderer.tsx index d8e6a1189..1cd803e23 100644 --- a/test/CustomRenderer.tsx +++ b/test/CustomRenderer.tsx @@ -20,36 +20,36 @@ export default function CustomRenderer() { [page, rotate, scale], ); - function drawPageOnCanvas() { - if (!page) { - return; - } - - const { current: canvas } = canvasElement; - - if (!canvas) { - return; - } - - const renderContext: RenderParameters = { - canvasContext: canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D, - viewport, - }; - - const cancellable = page.render(renderContext); - const runningTask = cancellable; - - cancellable.promise.catch(() => { - // Intentionally empty - }); - - return () => { - runningTask.cancel(); - }; - } - - // biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080 - useEffect(drawPageOnCanvas, [page, viewport]); + useEffect( + function drawPageOnCanvas() { + if (!page) { + return; + } + + const { current: canvas } = canvasElement; + + if (!canvas) { + return; + } + + const renderContext: RenderParameters = { + canvasContext: canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D, + viewport, + }; + + const cancellable = page.render(renderContext); + const runningTask = cancellable; + + cancellable.promise.catch(() => { + // Intentionally empty + }); + + return () => { + runningTask.cancel(); + }; + }, + [page, viewport], + ); return (