OS: Chrome Version 70.0.3538.110 (official Build) (64 bit)
React-pdf version: 1.0.0
"@react-pdf/renderer": "^1.0.0",
"@react-pdf/styled-components": "^1.2.0",
Description: With the last update made 7 days ago the console show this error, before the update the pdf works correctly.
_stream_readable.js:271 Uncaught Error: stream.push() after EOF
at readableAddChunk (_stream_readable.js:271)
at PDFDocument../node_modules/readable-stream/lib/_stream_readable.js.Readable.push (_stream_readable.js:245)
at PDFDocument._write (pdfkit.browser.es.js:3731)
at PDFReference.finalize (pdfkit.browser.es.js:255)
at PDFReference.end (pdfkit.browser.es.js:247)
at PNGImage.finalize (pdfkit.browser.es.js:3162)
at pdfkit.browser.es.js:3202
at Deflate.onEnd (index.js:225)
at Deflate../node_modules/events/events.js.EventEmitter.emit (events.js:96)
at endReadableNT (_stream_readable.js:1010)
at afterTickTwo (index.js:27)
at Item../node_modules/process/browser.js.Item.run (browser.js:153)
at drainQueue (browser.js:123)
Had the same error upgrading to 1.0.0 and it was because a second render was accessing the already finished stream from the last render that was still generating the PDF. In my case with React a second render was triggered in componentDidUpdate, while another one triggered by componentDidMount was still running (accessing the same stream) in the background. Solved the issue by creating a sequential queue:
import queue from 'queue'
const renderQueue = queue({
autostart: true, // Directly start when pushing.
concurrency: 1 // One concurrent job => run in series.
})
// Without a queue, render would happen in parallel, accessing the same
// stream, which will lead to "Error: stream.push() after EOF".
renderQueue.push(() => renderPDF())
I just saw this issue come through from my production app. I'm using the DOM bindings, so have a few levels of abstraction from the renderPDF method. I could see the same case @naminho outlined happening for me as I re-render the PDF when data changes, which may trigger the error if a previous rendering was still inflight.
Hey. Thanks for reporting this.
Could someone share a snippet I can use to reproduce this issue? I just tried making updates to a document and everything worked well.
This is what I did:
class App extends React.Component {
state = { counter: 10 }
componentDidMount() {
setInterval(() => {
if (this.state.counter > 0) {
this.setState({ counter: this.state.counter - 1 })
}
}, 2500)
}
render() {
return (
<PDFViewer style={{ width: '100%', height: '100%' }}>
<Document>
<Page>
<Text>{this.state.counter}</Text>
</Page>
</Document>
</PDFViewer>
)
}
}
I basically see the counter being decreased from 10 to 0 in the PDFViewer
Chrome version: 71.0.3578.98 (Official Build) (64-bit)
What happens if you set the interval to something really low like 5. I
think the issue is when the PDF is still rendering but another render is
called. Essentially you need to have two pdf rendering methods overlapping.
Might need to make the PDF more complex to slow down the rendering.
On Thu, Dec 13, 2018 at 6:29 PM Diego Muracciole notifications@github.com
wrote:
Hey. Thanks for reporting this.
Could someone share a snippet I can use to reproduce this issue? I just
tried making updates to a document and everything worked well.This is what I did:
class App extends React.Component {
state = { counter: 10 }componentDidMount() {
setInterval(() => {
if (this.state.counter > 0) {
this.setState({ counter: this.state.counter - 1 })
}
}, 2500)
}render() {
return (
{this.state.counter}
)
}
}I basically see the counter being decreased from 10 to 0 in the PDFViewer
—
You are receiving this because you commented.Reply to this email directly, view it on GitHub
https://github.com/diegomura/react-pdf/issues/420#issuecomment-447192014,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAcXfDJsUhynDaZJamR4Ne1OmNwd41THks5u4w0hgaJpZM4ZRowr
.
I render PDFDownloadLink in table cells. If that table renders only once there is no error. But my table renders twice because I do some DOM calculations. So I'm getting Uncaught Error: stream.push() after EOF.
Now I'm stuck how to overcome this :(
I don't see how queue can help here. Is there a way to cancel PDF generation in componentWillUnmount maybe? Or any other ideas?
@bkoltai I tried by also setting the timeout as 5, but still cannot reproduce this. What happens is I get the PDF with a 0 almost immediately. Could someone check if this is still present in the last version?
I actually think I narrowed my error usecase down to triggering a re-render from a componentDidMount|Update. Essentially, my component would mount, triggering a render of the PDF, and then immediately trigger a second render due to a state change. I imagine the first render never actually flushed to the DOM and the second render would throw the error, though I'm pretty naive about React internals, so not sure if that's accurate. Anyway, maybe triggering a re-render from a componentDidMount by calling setState might lead to the error, sorry I haven't been able to try for repro myself.
Thanks @bkoltai, but isn't my example doing what you said? It's basically triggering a re-render on componentDidMount when the counter get's decreased
Close I think, but might be different if it was just an immediate setState
rather than in a setInterval
On Tue, Jan 15, 2019 at 6:58 PM Diego Muracciole notifications@github.com
wrote:
Thanks @bkoltai https://github.com/bkoltai, but isn't my example doing
what you said? It's basically triggering a re-render on componentDidMount
when the counter get's decreased—
You are receiving this because you were mentioned.Reply to this email directly, view it on GitHub
https://github.com/diegomura/react-pdf/issues/420#issuecomment-454633498,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAcXfNQhzcEFInhmqX81XsyYzNnh0L80ks5vDpVNgaJpZM4ZRowr
.
Oh, got it! Going to try that. Thanks!
Thanks to @bkoltai comment I manage to do this by making sure only 1 of the PDF will load at a time.
The row renders a button that simply open a Modal. PDF is rendered here only when modal is open and I set shouldComponentUpdate to always return false.
This way there will never be any re-render and only 1 PDF component will render at a time. A bit troublesome but it works
I am also getting this error but in addition to the above problems my initial pdf document does not contain the properly rendered information. In the below example, this code should render a simple page with text on it but instead renders two blank pages:
import React from 'react';
import { Document, Page, View, Text, PDFViewer } from '@react-pdf/renderer';
import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap';
const myDoc = (
<Document>
<Page>
<View><Text>Test1</Text></View>
<View><Text>Test2</Text></View>
<View><Text>Test3</Text></View>
</Page>
</Document>
);
export default class Publish extends React.Component {
render() {
const { isOpen, toggle } = this.props;
return (
<Modal isOpen={isOpen} toggle={toggle} size={'lg'} className={'settings-modal'} >
<ModalHeader toggle={toggle}>Publish Test</ModalHeader>
<ModalBody>
<PDFViewer width={250} height={'100%'}>
{myDoc}
</PDFViewer>
</ModalBody>
<ModalFooter>
<Button onClick={toggle}>Close</Button>
</ModalFooter>
</Modal>
);
}
}
In a more complicated version of the above code, I have the ability to change the props of the component that renders the pdf. When the props change, the pdf document renders properly.
Noting issue #462 - the error message for that issue on codesandbox is "Potential infinite loop". Given that the above suggestions are about multiple renders causing the "stream.push() after EOF" error - could there be a correlation between these two issues?
I LOVE this library - thank you so much for creating it!
We are also seeing this in Firefox and IE too. @diegomura I wonder if we should look in to @naminho’s insight that “a second render was triggered in componentDidUpdate, while another one triggered by componentDidMount was still running (accessing the same stream) in the background”. I’m not sure where to start following the logic to find which stream is being pushed to after EOF, though.
I think this may (or may not) be related to #476, as we are also _not_ seeing the stream.push() after EOF error on Chrome, where the layout bug in #476 is not happening.
Same problem here, including Chrome :(
I faced the same problem while rendering PDFViewer in modal view the pdf thus rendered has blank pages and duplicate content. However, when rendering directly without modal no such issue persists. any update on this?
I couldn't get this to work in a modal, but I have a temporary hack that makes the page load, even when it's receiving props from another page. In this example I'm sending state from a Link to my Publish page, which generates a PDF.
On the first page we...
<Link to={{ pathname: '/publish', state: {foo: 'New Value'}}}>Click Here</Link>
Then that loads the below page...
import React from 'react';
import { PDFViewer, Document, Page, View, Text } from '@react-pdf/renderer'
export default class Publish extends React.Component {
constructor(props) {
super(props);
this.setStateFromLink = this.setStateFromLink.bind(this);
this.state = {
ready: true,
foo: 'Unchanged Value',
};
}
setStateFromLink(state) { // set state from incoming <Link>
if(state) {
const { foo } = state
this.setState({ foo })
}
}
componentDidMount() {
this.setStateFromLink(this.props.location.state)
// ************************************************************************************
// BEGIN HACKY BS - wait 1ms for props and state to settle before rendering the PDF
// react-pdf crashes if a re-render occurs when it's already rendering.
this.setState({ ready: false });
setTimeout(()=>{
this.setState({ ready: true });
}, 1);
// END *******************************************************************************
}
render() {
if (this.state.ready) {
return (
<React.Fragment>
<PDFViewer>
<Document>
<Page>
<View>
<Text>Foo: {this.state.foo}</Text>
</View>
</Page>
</Document>
</PDFViewer>
</React.Fragment>
)
} else {
return null
}
}
}
This successfully loads the value from the previous page by pausing the rendering of the Publish page for 1ms while the props & state sort themselves out. Then, with that 1ms passed, the page renders once and react-pdf successfully renders.
THIS IS HACKY! But I suppose it's a temp workaround until this multiple-render issue in react-pdf is resolved. Hope this helps someone (or feel free to tell me that I've broken the universe with this fix and that I shouldn't do it)
I couldn't get this to work in a modal, but I have a temporary hack that makes the page load, even when it's receiving props from another page. In this example I'm sending state from a Link to my Publish page, which generates a PDF.
On the first page we...
<Link to={{ pathname: '/publish', state: {foo: 'New Value'}}}>Click Here</Link>Then that loads the below page...
import React from 'react'; import { PDFViewer, Document, Page, View, Text } from '@react-pdf/renderer' export default class Publish extends React.Component { constructor(props) { super(props); this.setStateFromLink = this.setStateFromLink.bind(this); this.state = { ready: true, foo: 'Unchanged Value', }; } setStateFromLink(state) { // set state from incoming <Link> if(state) { const { foo } = state this.setState({ foo }) } } componentDidMount() { this.setStateFromLink(this.props.location.state) // ************************************************************************************ // BEGIN HACKY BS - wait 1ms for props and state to settle before rendering the PDF // react-pdf crashes if a re-render occurs when it's already rendering. this.setState({ ready: false }); setTimeout(()=>{ this.setState({ ready: true }); }, 1); // END ******************************************************************************* } render() { if (this.state.ready) { return ( <React.Fragment> <PDFViewer> <Document> <Page> <View> <Text>Foo: {this.state.foo}</Text> </View> </Page> </Document> </PDFViewer> </React.Fragment> ) } else { return null } } }This successfully loads the value from the previous page by pausing the rendering of the Publish page for 1ms while the props & state sort themselves out. Then, with that 1ms passed, the page renders once and react-pdf successfully renders.
THIS IS HACKY! But I suppose it's a temp workaround until this multiple-render issue in react-pdf is resolved. Hope this helps someone (or feel free to tell me that I've broken the universe with this fix and that I shouldn't do it)
I tried it now, it seems work correctly 👍
I solved my version of this issue with shouldComponentUpdate and the onRender callback.
createDocument = () => (
<Document onRender={() => { LOADING = false;}}>
<Page size="A4" style={styles.page}>
<View style={styles.section}>
<Text>Section #1</Text>
</View>
<View style={styles.section}>
<Text>Section #2</Text>
</View>
</Page>
</Document>
);
shouldComponentUpdate(nextProps, nextState, nextContext) {
return !LOADING
}
render () {
return <PDFViewer width={480} height={600}>{this.createDocument()}</PDFViewer>
}
I solved my version of this issue with shouldComponentUpdate and the onRender callback.
createDocument = () => ( <Document onRender={() => { LOADING = false;}}> <Page size="A4" style={styles.page}> <View style={styles.section}> <Text>Section #1</Text> </View> <View style={styles.section}> <Text>Section #2</Text> </View> </Page> </Document> ); shouldComponentUpdate(nextProps, nextState, nextContext) { return !LOADING } render () { return <PDFViewer width={480} height={600}>{this.createDocument()}</PDFViewer> }
Could you please release the whole snippet? We're facing the same issue.
I solved my version of this issue with shouldComponentUpdate and the onRender callback.
createDocument = () => ( <Document onRender={() => { LOADING = false;}}> <Page size="A4" style={styles.page}> <View style={styles.section}> <Text>Section #1</Text> </View> <View style={styles.section}> <Text>Section #2</Text> </View> </Page> </Document> ); shouldComponentUpdate(nextProps, nextState, nextContext) { return !LOADING } render () { return <PDFViewer width={480} height={600}>{this.createDocument()}</PDFViewer> }Could you please release the whole snippet? We're facing the same issue.
That was basically it. The LOADING variable was just defined globally. You don't get it to work ?
This example will only work for some use cases as it depends on what may cause a re-render and how often etc.
If you are re-rendering and changing the document this workaround could be better:
componentDidUpdate() {
const doc = this.createDocument();
ReactDOM.unmountComponentAtNode(document.getElementById('PDF_CONTAINER'));
if (this.state.width > 0 && this.state.height) {
ReactDOM.render(
<>
<PDFDownloadLink document={doc} fileName="report.pdf">
{({ blob, url, loading, error }) => (loading ? 'Loading document...' : 'Download')}
</PDFDownloadLink>
<PDFViewer width={this.state.width} height={this.state.height}>{doc
}</PDFViewer>
</>, document.getElementById('PDF_CONTAINER'))}
}
render () {
return <div id="PDF_CONTAINER" />
}
There are probably better ways. But as I understand it you can't update a document that is still rendering and since the pdf-rendering is async there is no guarantee that it's finished the next time the component want's to render. So either you'll take full control of when it updates ( 1st example) which says only render one at a time, or you just un-mount/re-mount the component with updated
props (2nd example).
@sondretj Thank you so much. We've now adapted your solution to React hooks and Functional components for our Typescript based app, and we'd like to share it here:
const Renderer: FC<RendererProps> = props => {
const [open, setOpen] = useState(false);
useEffect(() => {
setOpen(false);
setOpen(true);
return () => setOpen(false);
});
return (
<>
{open && (
<PDFViewer width={600} height={450}>
<Document>
<Page size="A4">{props.children}</Page>
</Document>
</PDFViewer>
)}
</>
);
};
export default Renderer;
I used this method for an on-the-fly pdf download where:
() => this.setState({ loadingPDF: false }) in my case since I want to set a loading state before and after this method call.export const createAndDownloadPDF = (pdfContent, filename, divId, callback) => {
setTimeout(() => {
const link = (
<div id={ divId }>
<PDFDownloadLink document={ pdfContent } fileName={ filename }></PDFDownloadLink>
</div>
)
const elem = document.createElement('div')
document.getElementById('root').appendChild(elem)
ReactDOM.render(link, elem)
setTimeout(() => {
document.getElementById(divId).children[0].click()
elem.remove()
callback()
}, 1);
}, 1)
}
Here is the method that is calling it (lives inside a React Component)
buildPDF = () => {
if (!this.state.loadingPDF) {
this.setState({ loadingPDF: true }, () => {
createAndDownloadPDF(this.getPDFContent(),
`someName.pdf`,
"pdf-creator-link",
() => this.setState({ loadingPDF: false })
)
})
}
}
The timeouts in the first method help only in the sense that they remove the render itself from the state cycle, and to let the document render the new div we are appending, respectively. So the setStates Im using for the loading behavior (showing a spinner next to the pdf icon while the PDF content renders) can be set while the pdf render does its thing. Note: this.getPDFContent() just return the Document I want to render using the ReactPDF components
Pros:
Cons:
In general Ive found that if I try to render the PDF while setting state for the component containing it that I get the stream.push() error. This makes it hard to render and hard to update the content, so I just create, render, and download the content on the fly using the above methodology until the core issue is fixed.
@youngbw I like your hack if you use the render props it gets easier:
<div id="divId">
<PDFDownloadLink document={invoiceTemplate} fileName={invoiceName}>
{({ blob, url, loading, error }) => {
if (!loading) {
setTimeout(() => {
document.getElementById('divId').children[0].click();
wipePDf();
}, 3);
}
}}
</PDFDownloadLink>
</div>
Wow, this thread has been really helpful. I was having similar errors attempting to render PDFDownloadLinks inside of react-inclusive-sortable-table. Every time I went to sort the table, I'd get a WSOD, because it was trying to re-render all the PDFs.
Anyways, I thought it might be helpful for posterity to document a component that puts together what @egemenuzunali and @youngbw were describing above:
import React from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types';
import Download from '../table/assets/icon-download.svg';
import { PDFDownloadLink } from '@react-pdf/renderer';
import slugify from 'slugify';
import CertificatePDF from './certificate-pdf';
export default class PDFLink extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false
};
this.generatePDF = this.generatePDF.bind(this);
this.buildPDF = this.buildPDF.bind(this);
this.createAndDownloadPDF = this.createAndDownloadPDF.bind(this);
}
createAndDownloadPDF(pdfContent, filename, divId, callback) {
setTimeout(() => {
const link = (
<div id={divId}>
<PDFDownloadLink document={pdfContent} fileName={filename}>
{({ loading }) => {
if (!loading) {
setTimeout(() => {
document.getElementById(divId).children[0].click();
callback();
}, 3);
}
}}
</PDFDownloadLink>
</div>
);
const elem = document.createElement('div');
document.getElementById('___gatsby').appendChild(elem);
render(link, elem);
}, 50);
}
buildPDF() {
if (!this.state.loading) {
this.setState({ loading: true }, () => {
this.createAndDownloadPDF(
this.generatePDF(),
`${slugify(this.props.title).toLowerCase()}.pdf`,
`${slugify(this.props.title).toLowerCase()}`,
() => this.setState({ loading: false })
);
});
}
}
generatePDF() {
// CertificatePDF is a component that returns a PDF <Document />
return <CertificatePDF {...this.props} />;
}
render() {
return this.state.loading ? (
'Loading...'
) : (
<button onClick={this.buildPDF}>
<Download
title={`Click here to download a certificate for ${this.props.title}`}
height="15"
width="12"
/>
</button>
);
}
}
PDFLink.propTypes = {
/* User name on the certificate */
name: PropTypes.string.isRequired,
/* Title of the course */
title: PropTypes.string.isRequired,
/* Date of completion */
date: PropTypes.string.isRequired,
/* Number of credits earned */
credits: PropTypes.string.isRequired
};
Any update on a proper sol'n for rendering the PDF in a modal? Thanks.
EDIT: Managed to implement a solution similar to something suggested above. Isolating the PDFViewer and throwing it in a child component and setting shouldComponentUpdate() equal to false seemed to have worked.
I had similar issue when using PDFDownloadLink. I solved it with using memo on the PDFDownloadLink.
const Link = (props) => {
return (
<PDFDownloadLink
fileName="file.pdf"
document={<Doc {...props} />}
>
Link
</PDFDownloadLink>
);
};
export const PDFLink = memo(Link, (prevProps, newProps) => {
//compare props and if no change, return true, else false
})
@kbrah - where does the memo() function come from?
@benjamingrobertson It's included in React import {memo} from 'react
Hi everyone,
I did the same like @benjamingrobertson only removing dependent parent divs and updating some things.
Just copy and paste:
https://gist.github.com/Italox/c1d53dd58d9a2a170272b469d9edbd79
Thx @benjamingrobertson 👍
Is there any other alternative to this library guys?? Can't spend hours now just to hack around it.
Is there any other alternative to this library guys?? Can't spend hours now just to hack around it.
I was frustrated just like you with this and other issues. Once I hacked around them, this library was very convenient to use.
I don't know what are you trying to accomplish, but maybe use Puppeteer https://github.com/GoogleChrome/puppeteer
Is there any other alternative to this library guys?? Can't spend hours now just to hack around it.
@paramsinghvc I've tried other, but despite of hack this is the better pdf generator in front-end. Can you try react-to-pdf, but this module take a screenshot of a children (ref) component. Maybe it work on your project.
I just implemented React.memo as @kbrah suggested, and it is a much cleaner (and less buggy) solution.
thanks @kbrah!!
It sounds like we've all had similar issues, and have arrived at similar solutions that all involve waiting for the data, either with some hackier-setTimeout functions (not criticizing - been there!)...my approach has been conditional rendering. Even when the app was working before this I was getting some fun errors, including the 'cannot setState on an unmounted component' etc. These all seem to be gone just by removing PDFViewer or PDFDownloadLink from the initial page load.
I'd be happy to show some code...my app chains together a whole mess of API calls using useEffect and async await etc...has several loading and error states, it would be a lot...
Save yourself the trouble - don't put the PDFViewer or PDFDownloadLink components right on the page with initial load! I've set up an initial view to return a div that has a form that asks for user input to fetch data first, or has a button you can click to use test data...that's it! Then it will return something else during loading, and then when I've set the state for loading back to false after the datga has arrived, only then will the app return the option for the user to preview or download. Seems to be working so far! Conditionally render everything!
Running into this same error with a different use case. I'm using react-router-dom to redirect a user to a user-specified slug (i.e. http://site.com/${slug}). I use a Switch to control route rendering, and at the path /:slug, my app renders the PDFViewer component. The redirect does not happen until after all my data is loaded and added to the state.
Everything works fine the first time the route is rendered, but subsequent renders on that route fail with this error (regardless of whether the route is navigated to from / OR /:slug).
I'm not exactly sure what the issue is in this case. Conditionally rendering the PDFViewer based on a loading state key doesn't solve the issue for me. Any ideas?
EDIT: The relevant code is here
@devinhalladay have you tried wrapping the Viewer component in React.memo in the same way I did PDFDownloadLink? (My previous answer)
@kbrah just tried it and didn't have much luck (though it's very possible I implemented React.memo incorrectly; still sort of new to React). However, this solution from @artemean just worked for me!
It's referenced above but I think #736 is related to this issue. I'm building out an interface that lets users control what things are rendered in the pdf, and neither React.memo nor returning false from shouldComponentUpdate are helping (the latter worked at first, until I needed to update props on the PDFViewer). Seems like the trick is to do everything on-demand, but this doesn't work when PDFViewer is already rendered and needs to be re-rendered several times. Could unmounting and remounting the PDFViewer component work?
EDIT: okay, the solution that has been most successful for my use case is to thread a pdfKey prop down through to my memoized PDFViewer, and implement it as <PDFViewer key={this.props.pdfKey}>...</PDFViewer>. Interaction with my control interface triggers a setState() on the parent component, which generates a new pdfKey and passes it back down the tree to re-generate the pdf component.
Hope that's useful for others with a similar use case!
I couldn't get this to work in a modal, but I have a temporary hack that makes the page load, even when it's receiving props from another page. In this example I'm sending state from a Link to my Publish page, which generates a PDF.
On the first page we...
<Link to={{ pathname: '/publish', state: {foo: 'New Value'}}}>Click Here</Link>Then that loads the below page...
import React from 'react'; import { PDFViewer, Document, Page, View, Text } from '@react-pdf/renderer' export default class Publish extends React.Component { constructor(props) { super(props); this.setStateFromLink = this.setStateFromLink.bind(this); this.state = { ready: true, foo: 'Unchanged Value', }; } setStateFromLink(state) { // set state from incoming <Link> if(state) { const { foo } = state this.setState({ foo }) } } componentDidMount() { this.setStateFromLink(this.props.location.state) // ************************************************************************************ // BEGIN HACKY BS - wait 1ms for props and state to settle before rendering the PDF // react-pdf crashes if a re-render occurs when it's already rendering. this.setState({ ready: false }); setTimeout(()=>{ this.setState({ ready: true }); }, 1); // END ******************************************************************************* } render() { if (this.state.ready) { return ( <React.Fragment> <PDFViewer> <Document> <Page> <View> <Text>Foo: {this.state.foo}</Text> </View> </Page> </Document> </PDFViewer> </React.Fragment> ) } else { return null } } }This successfully loads the value from the previous page by pausing the rendering of the Publish page for 1ms while the props & state sort themselves out. Then, with that 1ms passed, the page renders once and react-pdf successfully renders.
THIS IS HACKY! But I suppose it's a temp workaround until this multiple-render issue in react-pdf is resolved. Hope this helps someone (or feel free to tell me that I've broken the universe with this fix and that I shouldn't do it)
This hack worked for me very good! Thanks a lot. It works for both PDFViewer and PDFDownladLink in the same Modal.
That you sooooo much!!
This is my code for this and it works. Thanks to @modemmute
import React, { Component } from 'react';
import ReactPDF, {
Document, Page, Text, View, StyleSheet, PDFViewer, PDFDownloadLink,
} from '@react-pdf/renderer';
import {
Modal, ModalHeader, ModalBody, ModalFooter,
} from 'reactstrap';
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
showing: false,
ready: false,
};
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState((prevState) => ({
showing: !prevState.showing,
ready: false,
}), () => { // THIS IS THE HACK
setTimeout(() => {
this.setState({ ready: true });
}, 1);
});
}
render() {
const { showing, ready } = this.state;
const styles = StyleSheet.create({
page: {
flexDirection: 'row',
backgroundColor: '#FFF',
},
section: {
margin: 10,
padding: 10,
flexGrow: 1,
fontSize: 100,
fontWeight: 700,
color: '#CCC',
},
text: {
color: '#F00',
margin: 10,
padding: 10,
flexGrow: 1,
fontSize: 12,
},
});
const doc = (
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.section}>
<Text>This is a title</Text>
</View>
</Page>
</Document>
);
return (
<>
<Modal isOpen={showing} toggle={this.toggle}>
<ModalHeader toggle={this.toggle} />
<ModalBody>
{ready && (
<PDFViewer>
{doc}
</PDFViewer>
)}
</ModalBody>
<ModalFooter>
{ready && (
<PDFDownloadLink document={doc} fileName="test.pdf">
{
({ blob, url, loading, error }) => (loading ? 'Loading document...' : 'Download')
}
</PDFDownloadLink>
)}
</ModalFooter>
</Modal>
</>
);
}
}
export default MyComponent;
After quite some time struggling, I've used a PureComponent and it works perfectly.
Since Pure Components don't re-render blindly (by design), so does the PDFDownloadLink component. Hope it can help !
Same problem when using functional component to return PDFDownloadLink. Component works fine when the Document only has a page component as a child, but is the page has children it fails to render
In our case, the document component had required prop "data", which could be changed async.
So, we assumed that while PDFDownloadLink is loading, it was getting new data, which caused it to rerender before finishing initial loading, so our solution was, to not render at all PDFDownloadLink until final data is there.
{
(() => {
if (!reports.length) {
return null;
}
return (
<PDFDownloadLink
className="btn btn-primary"
document={<ReportPDF reports={reports} />}
fileName="report.pdf"
>
{({ blob, url, loading, error }) =>
loading ? "Loading document..." : "Download PDF"
}
</PDFDownloadLink>
);
})();
}
export const PDFLayout: React.SFC<PDFLayoutProps> = React.memo(({ children }) => {
const getLink = (link: string): any => window.open(link, '_blank') && <span />,
[ready, setReady] = useState(false);
return (
<PDFDownloadLink document={children} fileName="somefile.pdf">
{({ url, loading }) => {
if (!loading && !ready) {
setReady(true);
return null;
}
if (!loading && ready) {
return getLink(url);
}
}}
</PDFDownloadLink>
);
});
I am still getting the error. Can someone help me what I am missing?
@phoenixTW I've never worked with the memo, but this code should work
export const PDFLayout: React.SFC<PDFLayoutProps> = React.memo(({ children }) => {
const getLink = (link: string): any => window.open(link, '_blank') && <span />,
[ready, setReady] = useState(false);
if(!ready){
return null; // OR A PLACEHOLDER BUTTON
}
return (
<PDFDownloadLink document={children} fileName="somefile.pdf">
{({ url, loading }) => {
return getLink(url);
}}
</PDFDownloadLink>
);
});
if not ready don't return PDFDownloadLink at all, just return null or a placeholder button.
Then if everything is ready, then render PDFDownloadLink
Hey guys, there's a lot of code snippets above, but I've managed to fix the issue simply by putting dummy button that says 'Generate PDF' which triggers ready state, and with if check loaded PDFDownloadLink. Otherwise it would just rerender every time a component loads, which brought up the error. Hope this helps someone.
{ready ? (
<PDFDownloadLink
fileName={`${listing.title} - Investors Club Report`}
document={<DetailsPdf listing={listing} graphs={graphs} />}
>
{({ loading }) =>
loading ? (
<Loading isLoading />
) : (
<div className="btn btn--primary btn--med btn--full">
Download now!
</div>
)
}
</PDFDownloadLink>
)
I figured out, it was the data change which was causing this problem, so I used dcopy from library deep-copy, I first deep copied the data to be passed to pdf creator / viewer and then this problem is solved, actually it might be trying to render while the fetched data not yet returned completely. Anyway you can try this too if it works, for me I got rid of this frustrating situation :) Thanks for this nice Library :)
Here's how I fixed the problem, I hope it will help others.
Cannot read property 'dereference' of undefined
const GeneratePDF = props => {
const [isShowBlobProvider, setIsShowBlobProvider] = useState(false);
const downloadFullPDF = (blob, filename) => {
const a = document.createElement('a');
document.body.appendChild(a);
a.style = 'display: none';
a.href = window.URL.createObjectURL(blob);
a.download = filename;
a.click();
window.URL.revokeObjectURL(a.href);
a.remove();
setIsShowBlobProvider(false);
};
return (
<div>
{
isShowBlobProvider? (
<BlobProvider document={<Document />} fileName="somename.pdf">
{({ blob, url, loading, error }) => {
if (!loading) downloadFullPDF(blob, 'somename.pdf');
return (
<Button disabled variant="text" color="primary">
<CircularProgress size={24} />
Exporting
</Button>
);
}
)
}
<Button onClick={() => setIsShowBlobProvider(true)} variant="text" color="primary">
Export
</Button>
</div>
);
};
Thanks @bkoltai, but isn't my example doing what you said? It's basically triggering a re-render on
componentDidMountwhen the counter get's decreased
hi i am new to react but i got this problem as well. could you explain how to set a re-render on componentDidMount?
This is my code for this and it works. Thanks to @modemmute
import React, { Component } from 'react'; import ReactPDF, { Document, Page, Text, View, StyleSheet, PDFViewer, PDFDownloadLink, } from '@react-pdf/renderer'; import { Modal, ModalHeader, ModalBody, ModalFooter, } from 'reactstrap'; class MyComponent extends Component { constructor(props) { super(props); this.state = { showing: false, ready: false, }; this.toggle = this.toggle.bind(this); } toggle() { this.setState((prevState) => ({ showing: !prevState.showing, ready: false, }), () => { // THIS IS THE HACK setTimeout(() => { this.setState({ ready: true }); }, 1); }); } render() { const { showing, ready } = this.state; const styles = StyleSheet.create({ page: { flexDirection: 'row', backgroundColor: '#FFF', }, section: { margin: 10, padding: 10, flexGrow: 1, fontSize: 100, fontWeight: 700, color: '#CCC', }, text: { color: '#F00', margin: 10, padding: 10, flexGrow: 1, fontSize: 12, }, }); const doc = ( <Document> <Page size="A4" style={styles.page}> <View style={styles.section}> <Text>This is a title</Text> </View> </Page> </Document> ); return ( <> <Modal isOpen={showing} toggle={this.toggle}> <ModalHeader toggle={this.toggle} /> <ModalBody> {ready && ( <PDFViewer> {doc} </PDFViewer> )} </ModalBody> <ModalFooter> {ready && ( <PDFDownloadLink document={doc} fileName="test.pdf"> { ({ blob, url, loading, error }) => (loading ? 'Loading document...' : 'Download') } </PDFDownloadLink> )} </ModalFooter> </Modal> </> ); } } export default MyComponent;Please Help i got this error
```
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 InternalBlobProvider (created by PDFViewer)
```
Hey guys, there's a lot of code snippets above, but I've managed to fix the issue simply by putting dummy button that says 'Generate PDF' which triggers ready state, and with if check loaded PDFDownloadLink. Otherwise it would just rerender every time a component loads, which brought up the error. Hope this helps someone.
{ready ? ( <PDFDownloadLink fileName={`${listing.title} - Investors Club Report`} document={<DetailsPdf listing={listing} graphs={graphs} />} > {({ loading }) => loading ? ( <Loading isLoading /> ) : ( <div className="btn btn--primary btn--med btn--full"> Download now! </div> ) } </PDFDownloadLink> )
Very good, Thanks @sebastijandumancic I hadn't thought about it, it worked well
I was facing this problem. But finally I got the solution. So first of all make a state as following and then use it as conditionally at component return.
const [isReady, setIsReady] = useState(false);
useEffect(()=> {
setIsReady(true);
},[]);
return (
<>
{ isReady ? (
--------
rest line of code here.
--------
) :
('')
}
>
)
What the hell are all these weird workarounds etc.
It you want to render anything once, just use "useMemo":
const DownloadPdf = () => {
return useMemo(
() => (
<PDFDownloadLink document={<MyDocument />} fileName="some-nane.pdf">
{({ loading }) => (loading ? 'loading...' : 'download')}
</PDFDownloadLink>
),
[],
)
}
I believe folks like myself need to render it multiple times when the state it renders is changed. I too had to do a similar work-around.
In that case something like this would probably work:
const RerenderablePDF = (props) => {
return useMemo(
() => (
<PDFViewer key={Math.random()}>
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.section}>
<Text>This is a title</Text>
</View>
</Page>
</Document>
</PDFViewer>
),
[props],
)
}
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 InternalBlobProvider (created by PDFViewer)
Will be shown once, but i think it's harmless.
Changing the key and not reusing the previous rendered component is key. 😉
In that case something like this would probably work:
const RerenderablePDF = (props) => { return useMemo( () => ( <PDFViewer key={Math.random()}> <Document> <Page size="A4" style={styles.page}> <View style={styles.section}> <Text>This is a title</Text> </View> </Page> </Document> </PDFViewer> ), [props], ) }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 InternalBlobProvider (created by PDFViewer)
Will be shown once, but i think it's harmless.Changing the key and not reusing the previous rendered component is key. 😉
Finally found a use-case for useMemo(). @markpradhan Thank you! Works really well
@markpradhan what if I'm not using functional component and have a Class component?
Haven't tested it, but this should work.
class RerenderablePDF extends React.PureComponent {
render() {
const { whateverIsInProps } = this.props
return (
<PDFViewer key={Math.random()}>
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.section}>
<Text>This is a title</Text>
</View>
</Page>
</Document>
</PDFViewer>
)
}
}
Just make sure whatever you pass to props doesn't change unless you want the pdf to rerender. If updates happen frequently debouce/throttle them.
The above error was due to PDFViewer rendering in DOM as it's suppose to do based on the library but since the first render happens when the modal opens, the browser keeps that. when you close the modal, if there is no way to tell the browser something happen, it assumes there is we still at the previous render and so react-pdf Component(PDFViewer) will be still in the DOM without and instructions to render something, that's why on the second opening of the modal, it break with these error and no document to show.
Therefore, one solution would be to close the modal and reload the whole page which is not something you would want. Another simple solution, i'm using, is to use BlobProvider from the react-pdf/renderer to generate a url which you will pass to another Component from another library from npm react-pdf which renders correctly the data you need in the DOM. This is the code. Related to: https://github.com/diegomura/react-pdf/issues/420 and https://github.com/diegomura/react-pdf/issues/971.
import React, { useEffect, useState } from "react";
import Modal from "reactstrap/lib/Modal";
import { Button, ModalBody, ModalHeader } from "reactstrap";
import PropTypes from "prop-types";
import { Document, Page } from "react-pdf";
import { BlobProvider } from "@react-pdf/renderer";
import DocumentViewer from "./Documents/DocumentViewer";
const orngPrint = "/img/brand/print.png";
const MyDoc = props => {
// need to make props available for the document in BlobProvider
return <DocumentViewer {...props} />;
};
const ModalViewer = props => {
const [open, setOpen] = useState(false);
const [numPages, setNumPages] = useState(1);
const [pageNumber, setPageNumber] = useState(1);
const [url, setUrl] = useState(null);
const { stateChange, onExit, modalTitle } = props;
useEffect(() => {
// We are making a update to state so that when the modal closes, the document
// has a way to know that something happended in the component
// this is useful in this context because react-pdf/renderer has to be aware of the
// update to render our document correctly.
// Refer to this issues for more insights:
// - https://github.com/diegomura/react-pdf/issues/420
// - https://github.com/diegomura/react-pdf/issues/971
setOpen(false);
setOpen(true);
return () => setOpen(false);
}, []);
const handleExit = () => {
onExit();
setOpen(false);
};
const onDocumentLoadSuccess = ({ numPages }) => {
setNumPages(numPages);
};
return (
<div>
<Modal isOpen={true}>
<ModalHeader className="document-modal">
<Button className="cross-btn">
<i onClick={handleExit} className="fas fa-times" />
</Button>
</ModalHeader>
<ModalBody>
<div className="document-viewer-container">
{open && (
<BlobProvider document={MyDoc(props)}>
{({ url }) => {
// setUrl(url); # NOTE: you don't want to do this here. It creates another error
// The exatra Document is for rendering in the DOM
// since react/renderer does support dom rendering but
// it isn't stable. Requires to much configuration
return (
<Document
className={"document-inner"}
file={url}
onLoadSuccess={onDocumentLoadSuccess}
>
<Page
className={"document-container"}
pageNumber={pageNumber}
/>
</Document>
);
}}
</BlobProvider>
)}
</div>
</ModalBody>
</Modal>
</div>
);
};
ModalViewer.propTypes = {
modalTitle: PropTypes.string.isRequired
};
export default ModalViewer;
I fixed the issue when using a functional component, pdf viewer inside the modal.
`const DocumentPDFGenerator = ({ modalHandler }: IPdfGeneratorProps) => {
const [modal, setModal] = modalHandler;
const [ready, setReady] = useState(false);
const toggle = () => setModal(!modal);
useEffect(() => {
if (modal) {
setTimeout(() => {
setReady(true);
}, 1);
} else {
setReady(false);
}
}, [modal])
return (
<Modal isOpen={modal} toggle={toggle} size={'lg'}>
<ModalHeader toggle={toggle}>Modal title</ModalHeader>
<ModalBody>
{
ready && <PDFViewer className='w-100' style={{ height: '600px' }}>
<DocumentPdf />
</PDFViewer>
}
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={toggle}>Download</Button>{' '}
<Button color="secondary" onClick={toggle}>Cancel</Button>
</ModalFooter>
</Modal>
)
}
export default DocumentPDFGenerator`
Follow my code without solving the problem.
import React, { useState, useEffect, Fragment } from "react";
import { PDFViewer, Document, Page } from "@react-pdf/renderer";
import {
Table,
TableHeader,
TableCell,
TableBody,
DataTableCell,
} from "@david.kucsai/react-pdf-table";
import api from "./services/api";
import "./App.css";
function App() {
const [loading, setLoading] = useState(true);
const [condicoes, setCondicoes] = useState([]);
useEffect(() => {
async function loadCondicoes() {
const result = await api.get("condicoes");
setCondicoes(result.data);
if (result.data) {
setLoading(false);
//console.log(secoes);
}
}
loadCondicoes();
}, [loading]);
return (
<Fragment>
{loading && condicoes.length > 0}
<PDFViewer width="1000" height="600" className="app">
<Document>
<Page size="A4">
<Table data={condicoes}>
<TableHeader>
<TableCell>Id</TableCell>
<TableCell>Descrição</TableCell>
<TableCell>Pontuação</TableCell>
</TableHeader>
<TableBody>
<DataTableCell getContent={(r) => r.id} />
<DataTableCell getContent={(r) => r.descricao} />
<DataTableCell getContent={(r) => r.pontuacao} />
</TableBody>
</Table>
</Page>
</Document>
</PDFViewer>
</Fragment>
);
}
export default App;
Most helpful comment
@sondretj Thank you so much. We've now adapted your solution to React hooks and Functional components for our Typescript based app, and we'd like to share it here: