Generate Dynamic PDF Invoices Using React and React-PDF

alt='React-pdf invoice image’

Introduction

Generation of reports is a core feature in most applications. When developing for the web, one may opt to generate reports using HTML/CSS or PDF. In situations where HTML/CSS reports require emailing to third parties, these reports are exported to various formats one of them being PDF. It is therefore advantageous where the initial report generation is in PDF format as this will not require any exporting.

In this post, we step through the process of generating a simple dynamic PDF invoice using React and react-pdf by Diego Muracciole.

Prerequisites

There are no special requirements except an installation of ReactJS. However, a little knowledge of the workings of Flexbox will not hurt. This knowledge may be of value when laying out the components for the invoice.

Setting Up

Open the Terminal window or command prompt and change directory to the folder where you wish to create your project.

From the terminal window, run npx create-react-app react-pdf-invoice to create the project.

Change directory to the project folder by running cd react-pdf-invoice command

After the project has been created, run npm install @react-pdf/renderer --save from a terminal window to install @react-pdf/renderer package.

Start the application by running `npm start.

Generating Invoice Data

Generating test data manually is always a challenge. There are however many online generators that assist in the process of creating dummy data. For the invoice, we will be using JSON Generator by Vazha Omanashvili which uses a template to generate dummy data.

Using you browser, navigate to https://www.json-generator.com and replace the contents on the left side panel with the following template to generate the invoice data:

 {
    id: '{{objectId()}}',
    invoice_no: '{{date(new Date(2019, 0, 1), new Date(), "YYYYMM")}}-{{integer(100)}}',
    balance: '{{floating(1000, 4000, 2, "$0,0.00")}}',
    company: '{{company().toUpperCase()}}',
    email: '{{email()}}',
    phone: '+1 {{phone()}}',
    address: '{{integer(100, 999)}} {{street()}}, {{city()}}, {{state()}}, {{integer(100, 10000)}}',
    trans_date: '{{date(new Date(2019, 8, 1), new Date(2019,8,31), "YYYY-MM-dd")}}',
    due_date: '{{date(new Date(2019, 9, 4), new Date(2019,9,31), "YYYY-MM-dd")}}',
    items: [
      '{{repeat(5)}}',
      {
        sno: '{{index(1)}}',
        desc: '{{lorem(5, "words")}}',
        qty: '{{integer(2, 10)}}',
        rate: '{{floating(8.75, 1150.5, 2)}}'
      }
    ]
  }

Click on the Generate button to generate the data.

Creating Invoice Data File

In the project, create a folder within the src folder and name it data.

Within the data folder, create a new file and name it invoice-data.js

Update the file with the contents of the invoice data we generated.

Overview of Needed react-pdf Components

Alt Text

To structure our PDF invoice, the following components will be of interest to us.

  • <Document /> represents the PDF document itself. It must be the root of the document tree element structure. It should only contain children of type <Page /> component.

  • <Page /> represents a single page within a PDF document. <Page /> has a default size of A4 with a portrait orientation.

  • <View /> is the fundamental component used for building the UI. It is designed to be nested inside other views and can have zero or more child components.

  • <Text /> component is used for displaying text. It supports nesting of other <Text /> components.

  • <Image /> component is used for displaying images. Images may be JPG, PNG or base64 encoded image strings.

  • <PDFViewer /> component will be used for displaying the PDF documents within the web browser.

More information and other components can be found within the react_pdf documentation which is well written and simple to follow.

Create Invoice Main Component

Create a folder within the src folder and name it components.

Within the components folder, create a reports folder. This is optional if you wish to place all your files within the components folder.

Create a new file within the reports folder and name it Invoice.js

Update the Invoice.js with the following contents:

Update App.js File

Replace the contents of App.js file within the src folder with the following contents.

App.js is only laying out the <Invoice /> and passing data to the component. Even though we are using static data for this post, you can replace this with data fetched from a dynamic source like a database.

Open the command terminal and run npm start

A browser window should now open and display a blank PDF document.

Add Invoice Heading

Create a new component file within the reports folder and name it InvoiceTitle.js and update the new file using the following contents:

Open the Invoice.js file and append <InvoiceTitle title='Invoice' /> between the <Page /> component tags.

Add an import statement for the InvoiceTitle.js in the Invoice.js file import InvoiceTitle from './InvoiceTitle'

Save the changes and your PDF document in the browser should now display the invoice heading.

Create Invoice Number Component

The invoice number and transaction date are aligned to the right. We will therefore combine them into a single component.

Create InvoiceNo.js file within the reports folder and replace it with the following contents:

Within the Invoice.js file, import this new file and append the component below <InvoiceTitle title='Invoice' /> component.

Invoice Client Component

Create BillTo.js within the reports folder and replace with the following code:

Import this file within the Invoice.js file and add <BillTo invoice={invoice} /> below the <InvoiceNo invoice={invoice} /> component.

Invoice Items Table

Invoice items table component <InvoiceItemsTable /> is a composite component. It will be made up of <InvoiceTableHeader />, <InvoiceTableRow />, optional empty rows component <InvoiceTableBlankSpace /> and the <InvoiceTableFooter /> components. <InvoiceItemsTable /> will also store the constant number of rows that we require for the line items in our table.

Create InvoiceItemsTable.js within reports folder and insert the following code.

When you have updated the file, save the file and import InvoiceItemsTable.js within the Invoice.js file.

Append <InvoiceItemsTable invoice={invoice} /> below <BillTo invoice={invoice}/> within the Invoice.js

Invoice Items Table Header

This component follows the same approach like the previous components. We create InvoiceTableHeader.js and append the <InvoiceTableHeader /> between <View style={styles.tableContainer}> and </View> within the InvoiceItemsTable.js file. This component does not require any data to be passed.

Invoice Items Data Rows

For the invoice items data, we create InvoiceTableRow.js and import the component <InvoiceTableRow items={invoice.items}> below the <InvoiceTableHeader /> component within the InvoiceItemsTable.js file.

We will pass the invoice line items data to this component and using map function, we will iterate through the line items to create individual rows.

The following is the code for InvoiceTableRow.js.

Invoice Table Empty Rows

Invoices will have varying number of line items. To maintain a consistent size of invoice line items table, we will add blank rows to the invoice table. The number of rows added will be dependent on the lines of rows you require on your invoice and the number of line items in each invoice.

Create InvoiceTableBlankSpace.js and import the component <InvoiceTableBlankSpace rowsCount={ tableRowsCount - invoice.items.length> below the <InvoiceTableRow items={invoice.items} /> component within the InvoiceItemsTable.js file.

The number of blank rows passed to the component will be used to initialize a new array with a length equal to the number of required blank rows. The array elements will be filled with zeros.

Dashes will be used as cell values and the color for each cell a color of white to prenent them from being displayed. Without values (dash or any other value) for the blank cells, borders to this cells will be missing leading to inconsistencies in the items table layout.

The table footer will be used for totals and will only have two columns; one for the description and the other for the totals of the the line item amounts.

InvoiceTableFooter.js has the following code:

Thank You Message

We cannot forget to to thank our clients for doing business with us. Maybe this message will make them settle the invoices as early as is possible.

We therefore create InvoiceThankYouMsg.js file and import it within Invoice.js below <InvoiceItemsTable invoice={invoice} />

Thousand Words And a Picture

If you would like to add a logo to the invoice, copy the logo to the src folder or any other folder of your liking.

Add an import statement for the logo (import logo from '../../../src/logo.png') within the Invoice.js file

Add styling for the image within the styles section

    logo: {
        width: 74,
        height: 66,
        marginLeft: 'auto',
        marginRight: 'auto'
    }

Add the image <Image style={styles.logo} src={logo} /> above the <InvoiceTitle title='Invoice'/> component.

Styled Components

If you feel more comfortable using normal CSS, then you can take advantage of styled-components API inside your PDF documents. All you require to do is install react-pdf/styled-components in your project - yarn add @react-pdf/styled-components.

After installation, you can create styled components by importing styled object.

However, before diving deeply into using the package, be aware that react-pdf/styled-components package uses version 3 of styled-components while the main branch of styled-components is on version 4 with version 5 (Beast Mode) being in beta.

In reply to a Update to style-components v4 #1 pending issue, Diego Muracciole replied as follows:

I just checked this, and it wouldn't be that simple to implement. The main cause is that styled-components v4 does not export the lib dir but just the bundles. I'm not going to work on this in the near future, so if someone feels like tackling this, please do!

PDFViewer on Safari Browsers

If you are Safari browser, <PDFViewer /> displays the Print and Download button but nothing happens when the buttons are clicked. PDFViewer's Download and Print Button don't work on Safari is among pending issues but there are ways to resolve them.

To print a PDF document press Command + P buttons while the document is displayed and then click on the Print button.

To download a PDF document press Command + P buttons while the document is displayed, click on PDF dropdown at the botton left of the print dialog and then click on Save As PDF option.

alt='safari pdfviewer save as option’

Type a name for your document and click on Save.

To view various zoom in/out and page viewing options, right click on the document to select from available options.

Conclusion

In this post we stepped through the process of creating a dynamic PDF invoice using react-pdf. We started from a blank document and finally arriving at a completed PDF invoice. We also generated test data using JSON Generator.

The post would not have been complete without a mention of using styled-componentspackage while generating a PDF document. Styled-components are easy to work with but we pointed out an outstanding issue that one has to be aware of.

We ended by addressing printing and downloading issues that affect Safari browsers while using PDFViewer. Finally, we pointed at ways of going round those issues.

I hope the post will be of benefit to those using React and looking for ways to generate dynamic PDF documents.

The completed project is available on Github