diegomura / react-pdf

📄 Create PDF files using React
https://react-pdf.org
MIT License
14.37k stars 1.13k forks source link

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

Closed manolobattistadev closed 3 years ago

manolobattistadev commented 5 years ago

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)

antoniorrm commented 4 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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

bruinebeer commented 3 years ago

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 commented 3 years ago

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;
rizooooo commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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