Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArrayBuffer detached when upgrading from react-pdf from 6.2.2 to 7.5.1 #1657

Open
4 tasks done
Meess opened this issue Oct 28, 2023 · 9 comments
Open
4 tasks done

ArrayBuffer detached when upgrading from react-pdf from 6.2.2 to 7.5.1 #1657

Meess opened this issue Oct 28, 2023 · 9 comments
Labels
bug Something isn't working

Comments

@Meess
Copy link

Meess commented Oct 28, 2023

Before you start - checklist

  • I followed instructions in documentation written for my React-PDF version
  • I have checked if this bug is not already reported
  • I have checked if an issue is not listed in Known issues
  • If I have a problem with PDF rendering, I checked if my PDF renders properly in PDF.js demo

Description

When migrating from 6.2.2 to 7.5.1 the arrayBuffer becomes detached after use of <Document ... file={arrayBuffer} ... >. This can lead to some weird behaviour when arrayBuffer is reused (e.g. by using it as input react-pdf ).

I suspect the arrayBuffer reference is used as caching id, while array buffer becomes detached in 7.5.1.

I've tried a few workarounds, but one issue alway remains in 7.5.1 compared to 6.2.2: when loading a Pdf in 7.5.1 it always goes into loading state, instead of in 6.2.2 where, when using an ArrayBuffer, no loading state is shown when using the ArrayBuffer a second time after the component it was used in was unmounted. I don't know if this is intentional due to changes from 6.2.2 to 7.5.1 and underlying pdf.js library, as I can't test it in 7.5.1 with the original array buffer which becomes detached after first use.

Steps to reproduce

  • Load pdf with fetch as ArrayBuffer
  • Store response somewhere (in my case it's cached through react-query which caches responses)
  • Use arrayBuffer as file input for
  • first render arrayBuffer has size X
  • Pdf renders properly

Now let's assume some hooks will go off, e.g. for setting the number of pages on load

  • rerenders are triggered on component
    • 6.2.2: arrayBuffer stays size X
    • 7.5.1: arrayBuffer size 0, arrayBuffer is detached
  • Both versions render the Pdf properly
    • Here is why I suspect a reference is used as caching / useEffect / useCallback, in 7.5.1 the Pdf is properly rendered even though in the rerender the arrayBuffer is 0 / detached / empty.

Now do something that unmounts the component, e.g. move to a different page, or open another document, and then go back to the page with the original pdf of this example.

  • pdf arrayBuffer is taken from state (e.g. state manager or react-query response cache) and loaded into <Document ../> again
  • 6.2.2: Pdf is properly rendered
  • 7.5.1: Error trying to access detached arrayBuffer

Expected behavior

Expected the arrayBuffer to not be detached in 7.5.1, similar to 6.2.2

Actual behavior

arrayBuffer is detached in 7.5.1, deviating in behaviour from 6.2.2

Additional information

Workarounds I tried:

Make a blob out of it

  • const documentContent = new Blob([pdfArrayBuffer)], {type:'application/pdf'});
  • Fixes the arrayBuffer detached issue, but is not instantly loaded after unmounting and loading again ( i.e. shows loading state in 7.5.1 while no loading state in 6.2.2)

Make a copy of the array buffer on each render, and provide the copy to <Document ...> to the original is never detached:

const copyArrayBuffer = (arrayBuffer: ArrayBuffer) => {
    const copiedArrayBuffer = new ArrayBuffer(arrayBuffer.byteLength);
    new Uint8Array(copiedArrayBuffer).set(new Uint8Array(arrayBuffer));
    return copiedArrayBuffer;
};
  • Similar effect as making converting it to a blob

Make a Uint8Array of it:

  • const documentContent = new Uint8Array(pdfArrayBuffer);
  • <Document ...> will go into loading state forever

Environment

  • Browser (if applicable):
  • React-PDF version: 6.2.2 -> 7.5.1
  • React version: 18.2.0
  • Webpack version (if applicable): 5.89.0
@Meess Meess added the bug Something isn't working label Oct 28, 2023
@Meess
Copy link
Author

Meess commented Oct 28, 2023

A function / hook that preprocesses the File / ArrayBuffer which returns an intermediate representation that can be directly inserted in , which would effectively reduce or better remove the loading state all together.

Then the processing/preprocessing steps only have to happen once, just after the data is fetched, and not on rerenders or when the component is mounted again.

@Adelrisk
Copy link

@Meess Do any of your workarounds work? I believe I am being affected by this.

Ideally, I don't want to re-trigger the loading process if the data doesn't change, but I don't know how to achieve this.

@Adelrisk
Copy link

I believe I may have solved this issue given a hint from the documentation:

Make sure to define options object outside of your React component, and use useMemo if you can't.

The buffer is still being detached (length zero), but this doesn't lead to an error.

@croraf
Copy link

croraf commented Nov 19, 2023

I experience the same issue. After providing ArrayBuffer to the Document it has length 0 (no data).

As a workaround I have to do a copy of the old buffer (same as mentioned in the OP)

function copyArrayBuffer(originalBuffer) {
  // Create a new ArrayBuffer with the same byte length as the original
  const newBuffer = new ArrayBuffer(originalBuffer.byteLength);

  // Create TypedArray views for both the original and new buffers
  const originalView = new Uint8Array(originalBuffer);
  const newView = new Uint8Array(newBuffer);

  // Copy the data from the original buffer to the new buffer
  newView.set(originalView);

  return newBuffer;
}

@Meess
Copy link
Author

Meess commented Jan 3, 2024

@Meess Do any of your workarounds work? I believe I am being affected by this.

Ideally, I don't want to re-trigger the loading process if the data doesn't change, but I don't know how to achieve this.

@Adelrisk no we didn't find a workaround and stayed on 6.2.2 for the last few months, but are now accepting our faith and are migrating to 7.x . I went for the "Make a blob out of it" workaround to fix the empty arraybuffer, unfortunately that doesn't resolve the re-transformation of the array buffer to an actual pdf <Document file={blob} loading={<>Some loading state...</>} >...

I'm fetching the pdf as blob from our server now, with axios, so the arraybuffer won't be empty after the component is unmounted and mounted again:

const fetchPDF = async (url: string) => {
    const response = await axios.get<Blob>(url, { responseType: 'blob' });
    return response.data;
};

About useMemo, I'm not sure what improvement that will give as useMemo in React only memoizes from the previous render. So if your react component unmounts and then later mounts on a different page the arraybuffer will be 0 again.

At the moment I don't see a workaround for this as the library itself loads the pdf again if it's a blob in react-pdf/packages/react-pdf/src/Document.tsx

if (isBrowser) {
      // File is a Blob
      if (isBlob(file)) {
        const data = await loadFromFile(file);

        return { data };
      }
    }

@Adelrisk
Copy link

Adelrisk commented Jan 3, 2024

@Meess My above answer solved this issue for me. As code can be clearer than words.

bad:

import { pdfjs, Document } from "react-pdf";

export const PDFViewer = ({ pdflike }) => {
  // ...
  // The options inside the component are the problem
  const options = {
    // ...
  };
  return <Document file={pdflike} options={options}>
    <!-- ... -->
   </Document>
};

good:

import { pdfjs, Document, Outline, Page } from "react-pdf";

// The options outside the component fixed my problem.
const options = {
    // ...
};
export const PDFViewer = ({ pdflike }) => {
  // ...
  return <Document file={pdflike} options={options}>
    <!-- ... -->
   </Document>
};

I strongly suspect that the creation of a new options object on each render causes a re-rendering of the pdf component, because each object is not identical (in Javascript, {} != {} is always true). The pdf-rendering stack then breaks down, stuff goes wrong and a previously clean-up ArrayBuffer is accesses inappropriately causes the above error.

@Meess
Copy link
Author

Meess commented Jan 6, 2024

@Adelrisk I fully glanced over the "options" part, as I'm not using any options. So we might have a different issue! I'm not experiencing any issues with re-rendering of the component (i.e. the component stays mounted, but rerenders). But good to know about the "options" in case I start using it in the future, thanks!

When using 6.2.x react-pdf and using an ArrayBuffer as input for the pdf it behaved differently than 7.x.

  • Issue 1: I'm using react-query, which caches the response from the server (in this case the PDF arraybuffer), so when going to a different page and coming back to and early page which loaded a pdf it took the cached pdf arraybuffer,which is fine in 6.2.x but in 7.x is gives an empty / detached arraybuffer error.

    • I think you encountered this same issue, but as you are using the "options" you already got this issues when your component rerenderd (i.e. almost instant). While I didn't use options and only got it when going to a different page (component unmounts / not in react rendering tree anymore) and coming back (component is mounted again with the cached pdf arraybuffer is used). If you're using an arrayBuffer this might also still be the case for you if you cached the pdf arraybuffer, if you just fetch it on every page it shouldn't be an issue (although pdf's don't change that often so caching it might be beneficial for you too). But now I'm just guessing as I don't know your current stack!
  • issue 2: in 6.2.x it seems to use the arraybuffer and hold the pdf it created out of it in memory, so when we use the same arraybuffer it directly displays it, without a delay (i.e. the transformation of the arraybuffer to a pdf document that can be displayed can take ~1 or 2 seconds). But in 7.x because it comsumes / detaches the arraybuffer you can't reuse the array buffer and have to use a blob, however the pdf generated as result of a blob doesn't seems to be cached. So when going to a page for which I earlier already fetched the pdf it still shows a ~1 or 2 seconds of loading as it has to transform the blob to a displayable pdf again. Which isn't a big issue, but the ux felt way smoother with 6.2.x.

    • You might also have this, it's not a blocker but can be annoying if you're switching a lot between pdf's which are already loaded and have to wait ~1 or 2 secondes each time instead of instant.

    Hope that clarifies it a bit more, and makes it more clear how our issues relate / overlap.

Copy link
Contributor

github-actions bot commented Apr 8, 2024

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 github-actions bot added the stale label Apr 8, 2024
@obecker
Copy link

obecker commented Apr 10, 2024

I'm facing this issue during development, so it's not a production issue, but seeing this constantly after changing something in the sources of my react app is quite annoying.

In my case the PDF is opened as a local file in the browser. It renders fine, however, when i change something in the sources and the app gets automatically updated, I'm seeing an empty browser window with the mentioned errors in the console.

@github-actions github-actions bot removed the stale label Apr 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants