react-pdf-viewer
react-pdf-viewer copied to clipboard
No-op memory leak inside the CanvasLayer
Thanks for reporting the issue, @LucaHermann Are there any steps that I can reproduce the issue? Can you fork this sample project?
I basically every-time a PDF is open on the platform.
import React, { useState, useEffect, useRef } from "react"
import { useSelector, useDispatch } from "react-redux"
import { navigate, useLocation } from "@reach/router"
import ReactDOMServer from "react-dom/server"
import { alpha } from "@material-ui/core/styles"
import { Box, makeStyles } from "@material-ui/core"
import "@react-pdf-viewer/core/lib/styles/index.css"
import "@react-pdf-viewer/zoom/lib/styles/index.css"
import { zoomPlugin } from "@react-pdf-viewer/zoom"
import { Viewer, Worker } from "@react-pdf-viewer/core"
import Topbar from "./Topbar"
import Sidebar from "./Sidebar"
import { useQuery } from "../../../utils/hooks"
import { documentDownloadData } from "../../../actions/document"
import packageJson from "../../../../package.json"
// put the documentPreview query in the URL to trigger the opening of the DocumentPreview
export const openDocumentPreview = (documentID, navState) => {
let url = window.location.pathname
if (window.location.search) {
// look if documentPreview is already present and replace it with the new one
const documentPreviewRegexp = /documentPreview=[0-9]+/
if (documentPreviewRegexp.test(window.location.search))
url = window.location.search.replace(documentPreviewRegexp, `documentPreview=${documentID}`)
else
url += `${window.location.search}&documentPreview=${documentID}`
}
else
url += `?documentPreview=${documentID}`
if (navState)
return navigate(url, { state: navState })
navigate(url)
}
const HighlightText = ({ children }) => <span style={{
backgroundColor: alpha("#FF00", 1),
}} className="_preview-highlight">
{children}
</span>
export default function DocumentPreview({ documentID }) {
const classes = useStyles()
const dispatch = useDispatch()
const { applyDataroomFilters } = useQuery()
const { state: navState } = useLocation()
// get it from store to have the last updated version
const document = useSelector(state => state.documents.find(e => e.id === documentID))
const [viewer, setViewer] = useState({ type: null, data: null })
const [highlights, setHighlights] = useState([])
const topbarRef = useRef()
const pdfRef = useRef()
const windowWidth = useRef(0)
const zoomPluginInstance = zoomPlugin()
const { ZoomInButton, ZoomOutButton, ZoomPopover } = zoomPluginInstance
// put it here as there is some side effect when putting in a useEffect (missing height)
const viewerHeight = window.innerHeight - topbarRef.current?.clientHeight || 0
const viewerWidth = Math.round(windowWidth.current * 0.7)
useEffect(() => {
windowWidth.current = window.innerWidth
}, [])
// TODO UX: see if we had a snack content in case the user hasn't acess
useEffect(() => {
if (documentID && !document)
return navigate(window.location.pathname)
}, [documentID, document])
useEffect(() => {
if (document && document.content_type === ".tinymce")
setViewer({ type: "html", data: document.html_content })
else if (document && !document.blobData)
dispatch(documentDownloadData(documentID, true))
}, [document, document?.content_type, document?.blobData, documentID, dispatch])
useEffect(() => {
if (document?.blobData) {
let reader = new FileReader()
reader.readAsArrayBuffer(document.blobData)
reader.onloadend = function () {
setViewer({ type: "pdf", data: new Uint8Array(reader.result) })
}
}
}, [document, document?.blobData])
const removeOccurencies = () => {
let occurencies = [...window.document.getElementsByClassName("_preview-highlight")]
occurencies.forEach(occurency => occurency.offsetParent.innerHTML = occurency.offsetParent.innerText)
setHighlights([])
}
const findOccurrence = (str) => {
removeOccurencies()
if (str === "N/A")
return
const regexp = new RegExp(str, "i")
// get the div where every pages ar rerender individually
const innerPages = [...pdfRef.current.children[0].children[0].children[0].children]
let occurences = []
innerPages.forEach(page => {
// access the div where are located avery span who contain piece of text from the pdf
// if not accessible increase the amount of time in the timeout in documentLoad func
const textSpans = [...page.children[0].children[1]?.children || []]
if (textSpans) {
textSpans.forEach(item => {
// apply highlight
if (regexp.test(item.innerHTML)) {
item.innerHTML = item.innerHTML.replace(regexp,
(correspondance) => ReactDOMServer.renderToStaticMarkup(<HighlightText>{correspondance}</HighlightText>))
occurences.push(item)
}
})
}
})
if (occurences.length) {
// scroll to the first one
let innerPage = pdfRef.current.children[0].children[0].children[0]
setHighlights(occurences)
let topHeight = occurences[0].getBoundingClientRect().top
// we add +300 for better UX, if the DOM change, change it too
innerPage.scrollTop += (topHeight - 300)
}
}
// go the the next occurence based on a GOOD idx
const nextOccurence = (idx) => {
if (!highlights.length)
return
let innerPage = pdfRef.current.children[0].children[0].children[0]
let nextOccurence = highlights[idx]
let topHeight = nextOccurence.getBoundingClientRect().top
// we add +300 for better UX, if the DOM change, change it too
innerPage.scrollTop += (topHeight - 300)
}
// reset the URL without the documentPreview query
const handleClose = () => {
let nextUrl = window.location.pathname
if (applyDataroomFilters)
nextUrl += `?applyDataroomFilters=${applyDataroomFilters}`
navigate(nextUrl, { state: { navbar: navState?.navbar } })
}
const onDocumentLoad = () => {
// to force the render of each page in the DOM
// (100ms seems to be OK, less can be not sufficient on large pdf file)
let pageDiv = pdfRef.current.children[0].children[0].children[0]
pageDiv.scrollTop = pageDiv.scrollHeight
setTimeout(() => {
pageDiv.scrollTop = 0
}, 200)
}
if (!document)
return null
return (
<>
<div ref={topbarRef}>
<Topbar handleClose={handleClose} document={document} />
</div>
{viewerHeight > 0 && windowWidth.current > 0 &&
<Box display="flex">
<div style={{
padding: 24,
height: viewerHeight,
width: viewerWidth,
minWidth: viewerWidth,
backgroundColor: "grey",
}}>
{viewer.type === "pdf" ?
<Worker workerUrl={`https://unpkg.com/pdfjs-dist@${packageJson.dependencies["pdfjs-dist"]}/build/pdf.worker.min.js`}>
<div
className="rpv-core__viewer"
style={{
border: "1px solid rgba(0, 0, 0, 0.3)",
display: "flex",
flexDirection: "column",
height: "100%",
}}>
<div className={classes.viewerContainer}>
<ZoomOutButton />
<ZoomPopover />
<ZoomInButton />
</div>
<div ref={pdfRef} style={{ flex: 1, overflow: "hidden" }}>
<Viewer onDocumentLoad={onDocumentLoad} fileUrl={viewer.data}
plugins={[zoomPluginInstance]} />
</div>
</div>
</Worker>
:
<div className={classes.innerHtml}
dangerouslySetInnerHTML={{ __html: viewer.data }} />
}
</div>
<div style={{
height: viewerHeight,
flexGrow: 1,
overflowY: "auto",
}}>
<Sidebar document={document}
highlights={{ findOccurrence, nextOccurence, foundOccurences: highlights }} />
</div>
</Box>
}
</>
)
}
const useStyles = makeStyles(() => ({
innerHtml: {
backgroundColor: "white",
width: "100%",
height: "100%",
padding: 10,
overflow: "auto",
"& p": {
marginTop: 0,
}, "& h1": {
marginTop: 0,
}, "& h2": {
marginTop: 0,
}, "& h3": {
marginTop: 0,
}, "& h4": {
marginTop: 0,
}, "& h5": {
marginTop: 0,
}, "& h6": {
marginTop: 0,
}, "& pre": {
marginTop: 0,
},
},
viewerContainer: {
alignItems: "center",
backgroundColor: "#eeeeee",
borderBottom: "1px solid rgba(0, 0, 0, 0.1)",
display: "flex",
justifyContent: "center",
padding: "4px",
},
}))
Please create a minimal project that I can reproduce the issue. Otherwise, there is not much I can help.
