React-pdf: Can't perform a React state update on an unmounted component

Created on 13 Feb 2019  ·  19Comments  ·  Source: wojtekmaj/react-pdf

Before you start - checklist

  • [x] I followed instructions in documentation written for my React-PDF version
  • [x] I have checked if this bug is not already reported
  • [x] I have checked if an issue is not listed in Known issues

Description

With the following component, I have this warning:

Warning: Can't perform a React state update on an unmounted component.

screenshot 2019-02-13 at 18 59 36

Steps to reproduce

This is the problematic component:

class Pdf extends Component {
  state = { numPages: 0 }

  onDocumentLoad = ({ numPages }) => this.setState({ numPages })

  render() {
    const { numPages } = this.state
    const { url } = this.props

    const headers = { withCredentials: true }

    return (
      <Document file={{ headers, url }} onLoadSuccess={this.onDocumentLoad}>
        {range(0, numPages).map(index => (
          <Page key={index} pageIndex={index} />
        ))}
      </Document>
    )
  }
}

Expected behavior

If I remove the setState, the PDF renders properly but I lose the information about the number of pages.

Additional information

This is the example from the README and this was perfectly working before I upgraded to v4.

Environment

  • React-PDF version: 4.0.2
  • React version: 16.8.1
  • Webpack version (if applicable): 4.29.3
bug

Most helpful comment

I ran into this issue while using this library in a next.js project.

My understanding is that the error happens during rerenders - in my case, when onLoadSuccess is called to update numberOfPages.

My current solution is to use useMemo to memoize the file object. The error disappears completely if I do this.

function PreviewPDF(props) {
  const [numberOfPages, setNumberOfPages] = useState(null);
  const [pageNumber, setPageNum] = useState(1);
  const file = useMemo(
    () => ({ url: props.fileSrc, withCredentials: true }),
    []
  );

  return (
    <Portal>
      <Document
          file={file}
          onLoadSuccess={({ numPages }) => setNumberOfPages(numPages)}
          options={{ cMapUrl: '/_next/cmaps/', cMapPacked: true }}
        >
          <Page pageNumber={pageNumber} />
      </Document>
    </Portal>
  );
}

Where props.fileSrc is required.

Hope this helps.

All 19 comments

Hello @Kerumen ,

try this:

onDocumentLoad = (document) => {
  const { numPages } = document;
  this.setState({
    numPages,
  });
};

see the example here

@Kerumen did you get it working ?

@Maxhou00
It works fine when we load file directly from bundle. But when we load PDF from a URL same issue.

I tried the approach mentioned here but facing same issue as mentioned above it keeps on loading and keep making Network calls.
screen shot 2019-02-18 at 1 35 51 pm

"Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in PageInternal (created by Context.Consumer)
in Page (at Test.js:30)"

@qaisershehzad Perhaps you're defining new file object with every render? See #308

@wojtekmaj I am doing this :
screen shot 2019-02-18 at 4 55 52 pm

@wojtekmaj I am able to get it work by doing like this :
screen shot 2019-02-18 at 5 20 07 pm

I also had the same "React state update on an unmounted component" message. However, this seemed to happen when I had a multiple page PDF open in react-pdf, and was shifting from one page to another before the first page was loaded.

My "fix" was to disable all controls until the "onRenderSuccess" event was fired on a page load. Try to load a new page: disable all controls, wait for "onRenderSuccess". However, I don't know if this is overkill. @wojtekmaj : does this sound like the right thing to do?

We should be able to cancel setting state on PageInternal unmount, so this sounds like a bug that would be relatively easy to tackle. I don't think disabling all controls is worth it - the bug itself won't cause anything to crash - but it's a workaround that should do it if you really don't like seeing this message for now :)

Thank you for your comment. I'd look forward to that bug being fixed.

Hi @peterkmurphy, I am having the same issue but am not sure what you mean by disabling controls until onRenderSuccess gets called. I am doing this:

onRenderSuccess = () => {
  this.setState({
    renderSuccess: true
  });
};

...

{renderSuccess && numPages && numPages.lengh > 1 && (
  <>
    <p>
      Page {pageNumber || (numPages ? 1 : '--')} of{' '}
      {numPages || '--'}
    </p>
    <button
      type="button"
      disabled={pageNumber <= 1}
      onClick={this.previousPage}
    >
      Previous
    </button>
    <button
      type="button"
      disabled={pageNumber >= numPages}
      onClick={this.nextPage}
    >
      Next
    </button>
  </>
)}

Can you please elaborate on your solution? Thank you :)

Would also be interested in hearing the solution to this. Error is appearing in a component using React Hooks, so I'm not sure how that plays into the mix.

I ran into this issue while using this library in a next.js project.

My understanding is that the error happens during rerenders - in my case, when onLoadSuccess is called to update numberOfPages.

My current solution is to use useMemo to memoize the file object. The error disappears completely if I do this.

function PreviewPDF(props) {
  const [numberOfPages, setNumberOfPages] = useState(null);
  const [pageNumber, setPageNum] = useState(1);
  const file = useMemo(
    () => ({ url: props.fileSrc, withCredentials: true }),
    []
  );

  return (
    <Portal>
      <Document
          file={file}
          onLoadSuccess={({ numPages }) => setNumberOfPages(numPages)}
          options={{ cMapUrl: '/_next/cmaps/', cMapPacked: true }}
        >
          <Page pageNumber={pageNumber} />
      </Document>
    </Portal>
  );
}

Where props.fileSrc is required.

Hope this helps.

@qaisershehzad I've been struggling through it almost 2 days. Your approach with having the whole file object in state finally helped me. Thanks

Memoizing objects / keeping them in state is the correct, recommended solution. I'll make sure to make better documentation of that matter until we have some better solution (like the commit I just referenced above - work in progress, but "seems to work fine").

It will cancel request when unmount the component. The promise reject then.
https://github.com/wojtekmaj/react-pdf/blob/590077862132d86da44ac0dc59fc99aaf1e99224/src/Page.jsx#L77

  componentWillUnmount() {
    const { unregisterPage } = this.props;

    callIfDefined(
      unregisterPage,
      this.pageIndex,
    );

    cancelRunningTask(this.runningTask);
  }

In loadPage:
https://github.com/wojtekmaj/react-pdf/blob/590077862132d86da44ac0dc59fc99aaf1e99224/src/Page.jsx#L264

    try {
      const cancellable = makeCancellable(pdf.getPage(pageNumber));
      this.runningTask = cancellable;
      const page = await cancellable.promise;
      this.setState({ page }, this.onLoadSuccess);
    } catch (error) {
      this.setState({ page: false });
      this.onLoadError(error);
    }

But I think the error should be classified to determine it's a network error or cancelled tasks. If it's cancelled by unmount life cycle method, it should not setState or do anything else.
Like the code below:

    try {
      const cancellable = makeCancellable(pdf.getPage(pageNumber));
      this.runningTask = cancellable;
      const page = await cancellable.promise;
      this.setState({ page }, this.onLoadSuccess);
    } catch (error) {
        if (error.msg && error.msg === 'cancelled') {
            return;
        }

      this.setState({ page: false });
      this.onLoadError(error);
    }

@wojtekmaj

Facing the same error as well, when attempting to update numPages on a mounted component.
"Warning: Can't perform a React state update on an unmounted component."

Already using file in state - the issue is only when using onDocumentLoadSuccess:
onDocumentLoadSuccess = (document) => { console.log('doc loaded!') if(this.state.numPages == null) { this.setState({ numPages: document.numPages }) } }

Hmm, reproduced your onDocumentLoadSuccess and I can't see the error @scottie-schneider :( Wanna take a look?

https://codesandbox.io/s/little-hooks-bbgek

Hi,
I'm having the same issue but with a Buffer I'm loading from Redux, it gets into a loop. This only happens whenever I update the state on document load success.

Hmm, reproduced your onDocumentLoadSuccess and I can't see the error @scottie-schneider :( Wanna take a look?

https://codesandbox.io/s/little-hooks-bbgek

@wojtekmaj
it is working with you because your pdf contains only one page the error/warning still exists on multipage pdfs!

@msgfxeg Don't think that's the case (this repo is not affected), but using file object without memoizing sure can cause the issue desribed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wojtekmaj picture wojtekmaj  ·  4Comments

Crackiii picture Crackiii  ·  3Comments

Waize picture Waize  ·  4Comments

webguru103 picture webguru103  ·  3Comments

Vanals picture Vanals  ·  4Comments