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

Uncaught Error: stream.push() after EOF #420

Closed
manolobattistadev opened this issue Dec 13, 2018 · 62 comments · Fixed by #617
Closed

Uncaught Error: stream.push() after EOF #420

manolobattistadev opened this issue Dec 13, 2018 · 62 comments · Fixed by #617
Labels

Comments

@manolobattistadev
Copy link

manolobattistadev commented Dec 13, 2018

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)

@tobua
Copy link

tobua commented Dec 13, 2018

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

@bkoltai
Copy link
Contributor

bkoltai commented Dec 14, 2018

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.

@diegomura diegomura added the bug label Dec 14, 2018
@diegomura
Copy link
Owner

diegomura commented Dec 14, 2018

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)

@bkoltai
Copy link
Contributor

bkoltai commented Dec 14, 2018 via email

@artemean
Copy link

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?

@diegomura
Copy link
Owner

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

@bkoltai
Copy link
Contributor

bkoltai commented Jan 15, 2019

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.

@diegomura
Copy link
Owner

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

@bkoltai
Copy link
Contributor

bkoltai commented Jan 16, 2019 via email

@diegomura
Copy link
Owner

Oh, got it! Going to try that. Thanks!

@Vija02
Copy link

Vija02 commented Jan 18, 2019

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

@modemmute
Copy link

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 { 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!

@DuncanMacWeb
Copy link
Contributor

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.

@ycruzb
Copy link

ycruzb commented Feb 9, 2019

Same problem here, including Chrome :(

@geeksambhu
Copy link

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?

@modemmute
Copy link

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)

@manolobattista
Copy link

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 👍

@sondretj
Copy link

sondretj commented Mar 24, 2019

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

@FranzFlueckiger
Copy link

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.

@sondretj
Copy link

sondretj commented Apr 4, 2019

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

@FranzFlueckiger
Copy link

FranzFlueckiger commented Apr 8, 2019

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

@youngbw
Copy link

youngbw commented Jun 5, 2019

I used this method for an on-the-fly pdf download where:

  • PDFContent = my no-strings-added model from the ReactPDF kit
  • filename = the filename i want to export with
  • divId = just a unique string to use as the anchor point
  • callback = () => 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:

  • you can render the current state of your pdf even if its changed since you mounted its parent component
  • no stream.push() errors
  • no managing shouldComponentUpdate behavior

Cons:

  • A little slower since we are waiting to render until the PDF download button is pressed in this case, hence the Spinner i mentioned and the callbacks
  • Its a little hacky
  • Will be unnecessary when the issue of concurrent state updates interfering with the PDF render is solved (hopefully)

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.

@egemenuzunali
Copy link

egemenuzunali commented Jun 10, 2019

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

@benrobertsonio
Copy link

benrobertsonio commented Aug 1, 2019

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

@juanruben
Copy link

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!!

@juanruben
Copy link

juanruben commented Nov 14, 2019

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;

@Archymede123
Copy link

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 !

@cbutton01
Copy link

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

@youbek
Copy link

youbek commented Mar 16, 2020

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

@phoenixTW
Copy link

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?

@youbek
Copy link

youbek commented Mar 17, 2020

@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

@sebastijandumancic
Copy link

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

@DeepakKapiswe
Copy link

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

@vanmoortel
Copy link

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

@yfchina143
Copy link

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

hi i am new to react but i got this problem as well. could you explain how to set a re-render on componentDidMount?

@Rootwd
Copy link

Rootwd commented May 18, 2020

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)

@antoniorrm
Copy link

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

@AnkurBaliyan
Copy link

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

) :
('')
}
</>
)

@markpradhan
Copy link

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>
    ),
    [],
  )
}

@claytonfbell
Copy link

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.

@markpradhan
Copy link

markpradhan commented Jul 23, 2020

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

@frontendtony
Copy link

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

@hpelitebook745G2
Copy link

@markpradhan what if I'm not using functional component and have a Class component?

@bruinebeer
Copy link

bruinebeer commented Aug 2, 2020

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.

@asheux
Copy link

asheux commented Aug 13, 2020

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: #420 and #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;
  • This will render correctly.

@rizooooo
Copy link

rizooooo commented Oct 1, 2020

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`

@andrellgrillo
Copy link

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;

@mossa-Sammer
Copy link

for me what i need, is to generate an invoice and upload it to the cloud as a PDF when the user clicks the generate button,
a function from react-pdf will get rid of this Headache

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.