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

Can't perform a React state update on an unmounted component #353

Closed
3 tasks done
Kerumen opened this issue Feb 13, 2019 · 21 comments
Closed
3 tasks done

Can't perform a React state update on an unmounted component #353

Kerumen opened this issue Feb 13, 2019 · 21 comments
Assignees
Labels
bug Something isn't working stale

Comments

@Kerumen
Copy link
Collaborator

Kerumen commented Feb 13, 2019

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

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
@Maxhou00
Copy link

Maxhou00 commented Feb 14, 2019

Hello @Kerumen ,

try this:

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

see the example here

@qaisershehzad
Copy link

@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)"

@wojtekmaj
Copy link
Owner

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

@qaisershehzad
Copy link

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

@qaisershehzad
Copy link

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

@peterkmurphy
Copy link

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?

@wojtekmaj wojtekmaj added the bug Something isn't working label Feb 22, 2019
@wojtekmaj
Copy link
Owner

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 :)

@peterkmurphy
Copy link

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

@kevin-mon-liang
Copy link

kevin-mon-liang commented Apr 8, 2019

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 :)

@akuji1993
Copy link

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.

@hibearpanda
Copy link

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.

@shtabnoy
Copy link

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

wojtekmaj added a commit that referenced this issue Jul 27, 2019
Potentially fixes a common issue described #353 caused by creating objects in render method, but may introduce some behavioral differences e.g. refusing to load new PDF file with the same size.
@wojtekmaj
Copy link
Owner

wojtekmaj commented Jul 27, 2019

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").

@max-eb
Copy link

max-eb commented Aug 19, 2019

It will cancel request when unmount the component. The promise reject then.

cancelRunningTask(this.runningTask);

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

    callIfDefined(
      unregisterPage,
      this.pageIndex,
    );

    cancelRunningTask(this.runningTask);
  }

In loadPage:

try {

    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

@scottie-schneider
Copy link

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 }) } }

@wojtekmaj
Copy link
Owner

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 wojtekmaj self-assigned this Dec 7, 2019
@agustin-v
Copy link

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.

@msgfxeg
Copy link

msgfxeg commented Jul 21, 2020

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!

@wojtekmaj
Copy link
Owner

@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.

@github-actions
Copy link
Contributor

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 May 23, 2022
@github-actions
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working stale
Projects
None yet
Development

No branches or pull requests