react-pdf icon indicating copy to clipboard operation
react-pdf copied to clipboard

Caching loaded documents for reuse

Open ghost opened this issue 5 years ago • 7 comments

Before you start - checklist

  • [x] I have read documentation in README
  • [x] I have checked sample and test suites to see real life basic implementation
  • [x] I have checked if this question is not already asked

What are you trying to achieve? Please describe.

I have an application that can render multiple PDF documents. The user can select a document from the menu. Currently, whenever the user selects a document, the PDF file is always fetched anew from the server, even if it was already viewed prior. This puts a load on the network, as some documents can reach several MBs in size. I'm looking for a way to cache those PDF documents (incl. all pages) to avoid repeated requests (esp. on metered networks).

Describe solutions you've tried

I tried a naive approach to hook into onLoadSuccess and save the PdfDocumentProxy object into state

state = { pdfs: {} }

onDocumentLoadSuccess = pdf => {
  const selectedId = 'xyz'

  this.setState(({ pdfs }) => ({
    ...pdfs,
    [selectedId]: pdf
  }))
}

and then pass the saved object via file prop

render () {
  const { pdfs } = this.state
  const selectedUrl = 'https://example.com/example.pdf', selectedId = 'xyz'
  // If cached, use it, else fetch fresh from the API
  const pdf = pdfs[selectedId] ? pdfs[selectedId] : selectedUrl

  return <Document file={pdf} ... />
}

However, this doesn't work as file prop doesn't interpret PdfDocumentProxy objects. I also looked into other hooks such as onSourceSuccess, but it's not clear how I could retrieve the contents of the PDF file with either one.

Question

Is there any recommendations for caching PDFs with react-pdf? I looked into this in pdfjs-dist but didn't find anything conclusive. Any suggestions as far which store would be appropriate? I know of limitations of localStorage (5MB), IndexedDB (50MB/5MB), perhaps a POJO map could work? Or is this a silly idea altogether?

Environment

  • react-pdf: 4.0.2
  • react: 16.7.0
  • node: 10
  • Browser: IE11, Chrome, FF, Safari

ghost avatar Jan 16 '19 00:01 ghost

onLoadSuccess returns an object which represents a loaded document. You can't pass it back to file prop.

You can use fetch() to download the PDFs you need, and use FileReader on fetch() result to get a File or Blob object with a binary representation of the file, which you can later pass to file prop, which will be re-opened by React-PDF without downloading it again.

wojtekmaj avatar Jan 24 '19 13:01 wojtekmaj

@wojtekmaj There is a way to render the whole document/prefetch and cache all pages so we can have the blob cached and can be quickly redrawn?

Instead of calling pdf.getPage() on the next set of page numbers, which would fetch the data (I am not certain if it will create the image blob).

igoroctaviano avatar Sep 04 '19 12:09 igoroctaviano

@igoroctaviano I'd ask Mozilla on their pdf.js project site, but the solution could be also to simply handle downloading the file by yourself, and passing a Blob instead of an URL to React-PDF.

wojtekmaj avatar Dec 07 '19 19:12 wojtekmaj

@wojtekmaj Is there an example of this anywhere? I'm trying to implement the file download myself, and I'm creating a new Blob, however, react-pdf doesn't render anything. I'm not sure what I'm doing wrong..

mparisi76 avatar Jul 28 '20 14:07 mparisi76

@wojtekmaj Can you share an example of fetching file data then passing the data to the component?

daweimau avatar Sep 13 '21 08:09 daweimau

@daweimau,

const [PDFBlob, setPDFBlob] = useState(null)

useEffect(() => {
    const controller = new AbortController();

    fetch(url, { signal: controller.signal })
      .then(res => res.blob())
      .then(file => setPDFBlob(file))
      .catch(e => { 
        if(e.name !== 'AbortError') { /* handle error*/ }
      });

    return () => controller.abort();
  }, [url, setPDFBlob]);
  
  return PDFBlob && <Document file={PDFBlob} ...

ghost avatar Sep 19 '21 14:09 ghost

@daweimau,

const [PDFBlob, setPDFBlob] = useState(null)

useEffect(() => {
    const controller = new AbortController();

    fetch(url, { signal: controller.signal })
      .then(res => res.blob())
      .then(file => setPDFBlob(file))
      .catch(e => { 
        if(e.name !== 'AbortError') { /* handle error*/ }
      });

    return () => controller.abort();
  }, [url, setPDFBlob]);
  
  return PDFBlob && <Document file={PDFBlob} ...

That worked thanks!

NinjaAniket avatar May 27 '22 16:05 NinjaAniket

This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this issue will be closed in 14 days.

github-actions[bot] avatar Aug 29 '22 00:08 github-actions[bot]

This issue was closed because it has been stalled for 14 days with no activity.

github-actions[bot] avatar Sep 12 '22 00:09 github-actions[bot]