diegomura / react-pdf

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

Troubling getting this working with ES6 and passing in a component #338

Closed ghost closed 6 years ago

ghost commented 6 years ago

OS: Windows 10, but everything is running in Windows Subsystem for Linux (WSL) which is Ubuntu 18.04

React-pdf version: 1.0.0-alpha.18

Description: Frankly, I'm not good at translating the documentation into ES6, so part of them problem.

I have a component that passes several props to child components to render some query results. This is the "web view". I need to generate a printable version, that has a lot of different formatting from the web view (footers, headers, page numbers, page breaks, etc.), so am just wanting to use <PDFDownloadLInk> since I don't really need to render the component.

This is the parent component where I am calling the <PDFDownloadLink>:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { 
    PDFDownloadLink, 
    Document, 
    Page, 
    Text, 
    View 
} from '@react-pdf/renderer';

import {
    Col,
    Row
} from 'reactstrap';

import TestPrint from './bq_print';

class QueryResults extends Component {

    // derive the position name based on the current position
    getPositionName(pos) {
        // Logic to get the position name
    }

    renderResultsBody(cuts, defaultPercentiles, hourly) {
        // A whole bunch of stuff that calls other <Components />>
    }

    render() {
        // Set prop values
        let { 
            cuts,
            defaultPercentiles,
            hourly,
            pos_code,
            pos_desc,
        } = this.props;

        const MyDoc = (
            <Document>
                <Page>
                    <View>
                        <Text>Section #1</Text>
                    </View>
                    <View>
                        <Text>Section #2</Text>
                    </View>
                </Page>
            </Document>
        );

        return (
            <div className='bq-results'>
                <Row>
                    <Col>
                        <PDFDownloadLink document={
                            MyDoc
                            } fileName="somename.pdf">
                            {({ blob, url, loading, error }) => (
                                loading ? 'Loading document...' : 'Download now!'
                            )}
                        </PDFDownloadLink>
                    </Col>
                </Row>
                <Row className='bq-header'>
                    <h5 className='position-title'>
                        Position {pos_code} - {this.getPositionName(pos_code)}
                    </h5>
                </Row>
                <Row>
                    <p className='position-desc'>{pos_desc}</p>
                </Row>
                {this.renderResultsBody(cuts, defaultPercentiles, hourly)}
            </div>
        )
    }
}

function mapStateToProps(state) {
    return {
        cuts: state.results.cuts,
        defaultPercentiles: state.results.defaultPercentiles,
        hourly: state.results.hourly,
        pos_code: state.results.pos_code,
        pos_desc: state.results.pos_desc,
    }
}

export default connect (mapStateToProps, null)(QueryResults);

This does work and prints the document. Sweet.

However, I do not want any of the formatting in this component because it would be an absolute mess. I want to import another component <TestPrint /> which is where I want all the formatting for the PDF to take place (<Document>, <Page>, etc.). Trying to keep it clean and organized.

I removed the const MyDoc and modify the following:

<PDFDownloadLink document={
    <TestPrint />
    } fileName="somename.pdf">
    {({ blob, url, loading, error }) => (
        loading ? 'Loading document...' : 'Download now!'
    )}
</PDFDownloadLink>

To import this component:

import React, { Component } from 'react';
import { 
    Document, 
    Page, 
    View, 
    Text 
} from '@react-pdf/renderer';

export class TestPrint extends Component {

    // renders the search box that filters down the list of positions
    render() {
        return (
            <Document>
                <Page>
                    <View>
                        <Text>Section #1</Text>
                    </View>
                    <View>
                        <Text>Section #2</Text>
                    </View>
                </Page>
            </Document>
        )
    }
}

This generates the following area:

vendor.95dd6f48b697ea0410ad.js:29100 Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

How should I be approaching this?

How can I have all the PDF structure in one file and import it into the <PDFDownloadLink>?

Should I be using ReactPDF.render() and if so, how can I render the PDF for printing without rendering it in browser? Is the ${__dirname}/example.pdf a server location or will it automatically download for the user?

If it is just a VanillaJS solution, I'll also need to figure out how to pass the props back and forth parent and child components.

ghost commented 6 years ago

Ok, well the main issues was I forgot the default in the class declaration of <TestPrint />. Should have been export default class TestPrint extends Component {...}. Anyway, that cleared up the above error, but presented me with another error:

Warning: Please move inside a PDFViewer or passed to PDFDownloadLink or BlobProvider. Document as root will be deprecated in future versions

Ok, even though it is wrapped in a <PDFDownloadLink>. I tried adding <PDFViewer> to the parent and the child, but not at the same time, with the following error:

Uncaught Error: Invalid element of type iframe passed to PDF renderer

The parent looked like this:

<PDFDownloadLink document={
    <PDFViewer>
        <TestPrint />
    </PDFViewer>
    } fileName="somename.pdf">
    {({ blob, url, loading, error }) => (
        loading ? 'Loading document...' : 'Download now!'
    )}
</PDFDownloadLink>

The child looked like this:

render() {

    return (
        <PDFViewer>
            <Document>
                <Page>
                    <View>
                        <Text>Section #1</Text>
                    </View>
                    <View>
                        <Text>Section #2</Text>
                    </View>
                </Page>
            </Document>
        </PDFViewer>
    )
}

My questions still remain from the end of my first post.

ghost commented 6 years ago

One last variation before I call it quits for the day.

Got rid of the <PDFDownlink> to just use a regular button that changes this.state.print which when true should trigger ReactPDF.render(<TestPrint />,${__dirname}/example.pdf)... no dice. I am getting Uncaught TypeError: _renderer2.default.render is not a function and I am importing ReactPDF into the component with:

import ReactPDF, { PDFDownloadLink, PDFViewer, Document, Page, Text, View } from '@react-pdf/renderer';
diegomura commented 6 years ago

Hey! Thanks for reporting this. I’m on vacations now, answering from my phone so there’s no much I can do to help right now, rather some comments:

ghost commented 6 years ago

Hi, thanks for the quick response! Think I got it now. Needed to remove the <Document> from the child component and into the parent. Looked like this in the end:

<PDFDownloadLink document={
        <Document>
            <TestPrint />
        </Document>
    } fileName="somename.pdf">
    {({ blob, url, loading, error }) => (
        loading ? 'Loading document...' : 'Download now!'
    )}
</PDFDownloadLink>

And for the child component:

import React, { Component } from 'react';
import { 
    Page, 
    Text, 
    View, 
} from '@react-pdf/renderer';

export default class TestPrint extends Component {

    // renders the search box that filters down the list of positions
    render() {

        return (
            <Page>
                <View>
                    <Text>Section #1</Text>
                </View>
                <View>
                    <Text>Section #2</Text>
                </View>
            </Page>
        )
    }
}

So it works so far! Going to close the issue for now.

Enjoy your vacation!

smithywick commented 4 years ago

This was one of the top hits from google about passing in a valid Document and not a div.

This library is not designed to create a PDF of rendered HTML, IE the physical DOM output of your react.render(). This library is designed to create PDF's using its own components.