How to Generate Custom PDF, Using React and React-PDF.

jaymeeu

Abdulrasaq Jamiu Adewuyi

Posted on April 25, 2023

How to Generate Custom PDF, Using React and React-PDF.

Introduction

Generating a PDF from a webpage is becoming essential in web development, especially in a report-driven application. It is often used in scenarios ranging from creating an invoice in an e-commerce application, printing transactional reports, generating reports, etc. In this article, we will explore how to create an application that generates, prints, downloads, and shares a PDF with ReactJS and React-PDF.

You can also check out eduproject, explaining the design and implementation of custom PDF using React and React-pdf.

Together, we will go through step by step procedure I use to design an e-commerce invoice with the react-pdf package.

Page preview

The image below shows the webpage preview displaying download, print, and share buttons on every invoice.

project preview

PDF Invoice design

The image below shows the screenshot of a sample PDF invoice we will design for this project.

Invoice design

Page in action

The image below shows a preview of this project. Each invoice sample has download, print, and share buttons.

Project demo

Prerequisites

To follow along, we will need the following:

  • Basic knowledge of JavaScript and ReactJS.
  • Knowledge of CSS Flexbox property.
  • The source code of the project is available on GitHub.

Project Setup.

  • To get started with React. Run the below command in the terminal to create a new React project.

npx create-react-app pdf-gen

  • Change the directory to the project folder by running.

    cd pdf-gen

  • Next, let's install the required dependencies by running the below command.

npm install @react-pdf/renderer file-saver react-icons

  • Next, let’s create these files Invoice.js and PdfCard.js, we will be adding our code to them as we go along in this guide.

If using a Mac OS, let’s run the below command to create these files

touch src/Invoice.js touch src/PdfCard.js

If using a Windows OS, let’s run the below command to create the files, make sure to run the command one after the other.

cd.> src/Invoice.js

cd.> src/PdfCard.js

  • Then run the npm start to start the project. We should have a running react project ready to be modified.

Project Folder Structure

After the above setup process is completed, we should have our folder structure as below.

Basically, we will be working with the App.js file which is our root file, the Invoice.js, and PdfCard.js.

The react-pdf package will be used to design an e-commerce invoice sample. Before diving in properly, let’s work through the building block of the react-pdf package.

React-PDF

React-PDF is an open-source library for rendering PDF documents in a React application. It is a powerful and flexible library for working with PDF documents in React, making it an excellent choice for building PDF-related features in our projects.

React-PDF Building Blocks.

  • PDFViewer: This component will display the PDF documents within the web browser.
  • Document: This component represents the PDF document and is the parent component to other components.
  • Page: As the name implies, it represents a single page within a PDF document.
  • View: This is the core component for designing the UI.
  • Text: This component is used for displaying text
  • Image: This component displays images within the document. Support image formats such as JPG, PNG, GIF, etc.
  • PDFDownloadLink: This component creates a download link for a PDF document generated by the React-pdf library.
  • BlobProvider: This component generates a PDF document and provides it as a Blob object that can be used to display or download the PDF document.

With this out of the way, let's create our PDF.

Image description

Design an E-commerce PDF Invoice

In our project folder, let's create a new file called Invoice.js in the src folder and add code as shown below.

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

    const Invoice = () => {

    const reciept_data = {
    // update reciept_data here
    }

    const styles = StyleSheet.create({
    // update Invoice styles here 
    }}

    const InvoiceTitle = () => (
    // update InvoiceTitle component here 
    );

    const Address = () => (
    // update Address component here
    );

    const UserAddress = () => (
    // update UserAddress component here
    );

    const TableHead = () => (
    // update TableHead component here
    );

    const TableBody = () => (
    // update TableBody component here
    );

    const TableTotal = () => (
    // update TableTotal component here
    );

      return (
            <Document>
                <Page size="A4" style={styles.page}>
                    <InvoiceTitle  />
                    <Address/>
                    <UserAddress/>
                    <TableHead/>
                    <TableBody/>
                    <TableTotal/>
                </Page>
            </Document>
      )
    }
    export default Invoice
Enter fullscreen mode Exit fullscreen mode

The above code snippet shows the Invoice component which includes other sub-components which are InvoiceTitle, Address, UserAddress, TableHead, TableBody, and the TableTotal components, these components will be updated accordingly as we go along in this guide.

The above image shows the section and position of each component in the Invoice design sample.

Open the App.js file and replace the existing code with code as below:

 import { PDFViewer } from "@react-pdf/renderer";
    import Invoice from "./Invoice";

    function App() {
      return (
        <div>
          <PDFViewer width="1000" height="650" className="app" >
            <Invoice />
          </PDFViewer>
        </div>
      );
    }

    export default App;
Enter fullscreen mode Exit fullscreen mode

Next, let's update invoice styles as below. This includes the stylesheet for styling and beautifying our invoice

Next, let's update InvoiceTitle component and make sure to import a logo for the Image.

const InvoiceTitle = () => (
            <View style={styles.titleContainer}>
                <View style={styles.spaceBetween}>
                    <Image style={styles.logo} src={logo} />
                    <Text style={styles.reportTitle}>Xpress Enterprises</Text>
                </View>
            </View>
        );
Enter fullscreen mode Exit fullscreen mode

Next, let's update Address component.

const Address = () => (
            <View style={styles.titleContainer}>
                <View style={styles.spaceBetween}>
                    <View>
                        <Text style={styles.invoice}>Invoice </Text>
                        <Text style={styles.invoiceNumber}>Invoice number: {reciept_data.invoice_no} </Text>
                    </View>
                    <View>
                        <Text style={styles.addressTitle}>7, Ademola Odede, </Text>
                        <Text style={styles.addressTitle}>Ikeja,</Text>
                        <Text style={styles.addressTitle}>Lagos, Nigeria.</Text>
                    </View>
                </View>
            </View>
        );
Enter fullscreen mode Exit fullscreen mode

Next, let's update UserAddress component.

const UserAddress = () => (
            <View style={styles.titleContainer}>
                <View style={styles.spaceBetween}>
                    <View style={{maxWidth : 200}}>
                        <Text style={styles.addressTitle}>Bill to </Text>
                        <Text style={styles.address}>
                            {reciept_data.address}
                        </Text>
                    </View>
                    <Text style={styles.addressTitle}>{reciept_data.date}</Text>
                </View>
            </View>
        );
Enter fullscreen mode Exit fullscreen mode

Next, let's update TableHead component.

 const TableHead = () => (
            <View style={{ width:'100%', flexDirection :'row', marginTop:10}}>
                <View style={[styles.theader, styles.theader2]}>
                    <Text >Items</Text>   
                </View>
                <View style={styles.theader}>
                    <Text>Price</Text>   
                </View>
                <View style={styles.theader}>
                    <Text>Qty</Text>   
                </View>
                <View style={styles.theader}>
                    <Text>Amount</Text>   
                </View>
            </View>
        );
Enter fullscreen mode Exit fullscreen mode

Next, let's update TableBody component.

const TableBody = () => (
           reciept_data.items.map((receipt)=>(
            <Fragment key={receipt.id}>
                <View style={{ width:'100%', flexDirection :'row'}}>
                    <View style={[styles.tbody, styles.tbody2]}>
                        <Text >{receipt.desc}</Text>   
                    </View>
                    <View style={styles.tbody}>
                        <Text>{receipt.price} </Text>   
                    </View>
                    <View style={styles.tbody}>
                        <Text>{receipt.qty}</Text>   
                    </View>
                    <View style={styles.tbody}>
                        <Text>{(receipt.price * receipt.qty).toFixed(2)}</Text>   
                    </View>
                </View>
            </Fragment>
           ))
        );
Enter fullscreen mode Exit fullscreen mode

Next, let's update TableTotal component.

const TableTotal = () => (
            <View style={{ width:'100%', flexDirection :'row'}}>
                <View style={styles.total}>
                    <Text></Text>   
                </View>
                <View style={styles.total}>
                    <Text> </Text>   
                </View>
                <View style={styles.tbody}>
                    <Text>Total</Text>   
                </View>
                <View style={styles.tbody}>
                    <Text>
                        {reciept_data.items.reduce((sum, item)=> sum + (item.price * item.qty), 0)}
                    </Text>  
                </View>
            </View>
        );
Enter fullscreen mode Exit fullscreen mode

Once all components and pages are updated, we should see our PDF displayed on the screen as below.

let get started

Now we have a PDF Invoice file that can be downloaded, printed, and shared via email.
In a real-world scenario, it is likely we have an Invoice or list of invoices requiring actions such as downloading, printing, and sharing via email. Using some custom hooks from React-PDF, we can implement these features. Let's update our design to accommodate these action buttons (download, print, and share).

Let's create a new file PdfCard.js update as below:

import React from 'react'
    import {CgFileDocument} from 'react-icons/cg'
    import {HiOutlineDownload, HiOutlinePrinter} from 'react-icons/hi'
    import {FiShare2} from 'react-icons/fi'

    const PdfCard = ({title}) => {

        const styles = {
            container : {  width:'220px',  borderRadius : '5px',  padding:'15px 12px',  display:'flex',  flexDirection:'column',  gap:'15px',  boxShadow: "0 3px 10px rgb(0 0 0 / 0.2)"},
            flex : { width:'100%', display:'flex', gap:'5px', alignItems:'center' },
            bold : { fontSize:'13px', fontWeight: 600},
            thin : {  fontSize:'11px',  color:'#6f6f6f',  fontWeight: 500 },
            btn:{ borderRadius : '3px', border:'1px solid gray', display : 'flex', alignItems :'center', gap:'2px', padding : '3px', fontSize:'11px', color:'#4f4f4f', fontWeight: 600, cursor:'pointer', userSelect:'none'}
        }

      return (
        <div style={styles.container}>
            <div style={styles.flex}>
                <CgFileDocument color='#90e0ef' size={20}/>
                <span style={styles.bold}>{title}</span>
            </div>
            <div style={styles.thin}>
                Lorem ipsum dolor sit amet consectetur adipisicing elit. Ducimus eligendi reiciendis fuga doloremque
            </div>

            <div style={{...styles.flex, ...{justifyContent:'space-between'}}}>
                <div style={styles.btn}>
                    <HiOutlineDownload size={14}/>
                    <span>Download</span>
                </div>

                <div style={styles.btn}>
                    <HiOutlinePrinter size={14}/>
                    <span>Print</span>
                </div>
                <div style={styles.btn}>
                    <FiShare2 size={14}/>
                    <span>Share</span>
                </div>
            </div>


        </div>
      )
    }

    export default PdfCard
Enter fullscreen mode Exit fullscreen mode

To see this design in action, let's update the App.js as below:

 import PdfCard from "./PdfCard";

    function App() {
      const cards = {  maxWidth: "1200px", margin: "0 auto", display: "grid", gap: "1rem", padding : '20px', gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))"}
      return (
        <div>
          <h2 style={{textAlign:'center'}}>List of invoices</h2>
          <div style={cards}>
            <PdfCard title="Oasic ltd Invoice"/>
            <PdfCard title="Libra ltd Invoice"/>
            <PdfCard title="Xpress ltd Invoice"/>
            <PdfCard title="Cardic ltd Invoice"/>
          </div>
        </div>
      );
    }

    export default App;
Enter fullscreen mode Exit fullscreen mode

We should see the webpage below:

page preview

Next, let's implement the actions button functionalities.

Downloading PDF Invoice with React-PDF

To download PDF we will be using a custom hook PDFDownloadLink from React-PDF. This component creates a download link for a PDF document generated by the react-pdf library.
Let's update our PdfCard.js with the PDFDownloadLink as below:

<PDFDownloadLink document={<Invoice />} fileName='invoice.pdf'>
       <div style={styles.btn}>
          <HiOutlineDownload size={14}/>
          <span>Download</span>
       </div>
     </PDFDownloadLink>
Enter fullscreen mode Exit fullscreen mode

Ensure to import PDFDownloadLink from @react-pdf/renderer and Invoice from Invoice.js. Once updated, click the download button and see the invoice download to the localdisk of the computer. With that done, let's move on to the Print functionality.

Printing PDF Invoice with React-PDF

To print PDFs, we will use a custom hook BlobProvider from React-PDF. This component generates a PDF document and provides it as a Blob object that can be used to display or download the PDF document.

Let's update our PdfCard.js with the BlobProvider as below:

<BlobProvider document={<Invoice />}>
      {({ url, blob }) => (
         <a href={url} target="_blank" style={styles.btn}>
            <HiOutlinePrinter size={14}/>
               <span>Print</span>
         </a>
      )}
     </BlobProvider>
Enter fullscreen mode Exit fullscreen mode

Ensure to import BlobProvider from @react-pdf/renderer. Once updated, click the print button and see the invoice preview in a new tab. And finally, let's implement the Share functionality.

Share PDF Invoice with React-PDF

To share PDFs via email, we will use a custom hook, BlobProvider from React-PDF. This implementation will download a PDF file to the computer's local disk and open a mailbox for us to attach the downloaded file.

To see this in action, let's update our PdfCard.js as below:

import { saveAs } from "file-saver";

    const handleShare = async (blob) => {
            await saveAs(blob, `invoice.pdf`);
            window.location.href = `mailto:?subject=${encodeURIComponent(`Invoice`)}&body=${encodeURIComponent(`Kindly find attached invoice`)}`;
        }
    ...
    ...
    ...

     <BlobProvider document={<Invoice />}>
       {({ url, blob }) => (
         <div style={styles.btn} onClick={() => handleShare(url, blob)} >
            <FiShare2 size={14} />
            <span>Share</span>
         </div>
       )}
    </BlobProvider>

Enter fullscreen mode Exit fullscreen mode

Conclusion

In this article, we built an application that generates, prints, downloads, and shares a PDF with ReactJS and React-PDF.

To learn more about React-PDF, check out its official documentation.

If this article is helpful, consider liking, sharing, and following me; connect with me on Twitter for more articles like this.

Happy coding.

💖 💪 🙅 🚩
jaymeeu
Abdulrasaq Jamiu Adewuyi

Posted on April 25, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related