Before you start - checklist
Description
With the following component, I have this warning:
Warning: Can't perform a React state update on an unmounted component.
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
4.0.2
16.8.1
4.29.3
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.
"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 :
@wojtekmaj I am able to get it work by doing like this :
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?
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?
@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.
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 updatenumberOfPages
.My current solution is to use
useMemo
to memoize the file object. The error disappears completely if I do this.Where
props.fileSrc
is required.Hope this helps.