Generate Dynamic PDF With Custom Style In React

“A person who never made a mistake never tried anything new.” - Albert Einstein

Overview

This article is a step-by-step guide to generate dynamic PDF. Also, view and download it in ReactJs using React-pdf library. We will also understand the basic components of the React-pdf library with real life examples.

What is React-pdf?

It is a popular and widely used library for creating PDF files on the browser and server.

This library offers some basic components and you can customize them to come up with amazing designs. Let’s see some components which we will use later.

  • <Document> : This component represents the PDF document itself. And this is the root of a PDF file.
  • <Page> : This tag represents a single page inside the PDF documents.
  • <View> : This is a general purpose container used to build a UI for PDFs.
  • <Image> : Used to display network or local(Node only) JPG or PNG images into a PDF.
  • <Text> : Used to display text.
  • <PDFDownloadLink> : This is an anchor tag to enable generating and downloading PDF documents.
  • <PDFViewer>: This tag is used to render client-side generated documents.

Step 1 - Creating Project

Open the terminal window, run below command to create the project. It will take some time to create the initial project structure and install default packages.

npx create-react-app projectName

Then change directory to the project folder by running the below command.

cd projectName

Step 2 - Installing NPM Package

After the project has been created, install @react-pdf/renderer package

Using npm,

npm install @react-pdf/renderer –save

Or

Using Yarn,

yarn add @react-pdf/renderer

Now, start the application by running npm start command.

Step 3 - Generating Dummy Invoice Data

Generating test JSON data manually is always a challenge. For creating dummy invoice data, we will be using JSON Generator.

Using 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(2021, 11, 1), new Date(2021,11,31), "dd-MM-YYYY")}}',
    due_date: '{{date(new Date(2021, 11, 4), new Date(2021,12,31), "dd-MM-YYYY")}}',
    items: [
      '{{repeat(3)}}',
      {
        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.

Step 4 - Creating Invoice Data File

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

Within the jsonData folder, create a new file and name it InvoiceData.js.

Add the contents of the invoice data we generated in this file.

const InvoiceData = {
    id: "5df3180a09ea16dc4b95f910",
    invoice_no: "201906-28",
    balance: "$2,283.74",
    fullname: "MANTRIX",
    email: "[email protected]",
    phone: "+1 (872) 588-3809",
    address: "922 Campus Road, Drytown, Wisconsin, 1986",
    trans_date: "26-11-2021",
    due_date: "26-11-2021",
    companyID: "10001",
    companyName: "xyz company",
    items: [
        {
            sno: 1,
            desc: "FinePix Pro2 3D Camera",
            qty: 2,
            rate: 1600.00,
        },
        {
            sno: 2,
            desc: "Luxury Ultra thin Wrist Watch",
            qty: 1,
            rate: 300.99,
        },
        {
            sno: 3,
            desc: "Duracell Ultra Alkaline Battery AA, 4 pcs",
            qty: 1,
            rate: 145.99,
        }
    ]
}

export default InvoiceData;

Step 5 - Create Invoice Main Component

Create a folder within the src folder and name its components. Within the components folder, create a generateInvoice folder.

Create a new file within the generateInvoice folder and name it Invoice.js and add the following contents in the new file.

import React from "react";
import { Page, Document, StyleSheet, Image } from "@react-pdf/renderer";
import logo from "../../assets/react-pdf-icon.png";
import InvoiceTitle from "./InvoiceTitle";
import InvoiceNo from "./InvoiceNo";
import BillTo from "./BillTo";
import InvoiceThankYouMsg from "./InvoiceThankYouMsg";
import InvoiceItemsTable from "./InvoiceItemsTable";

const styles = StyleSheet.create({
    page: {
        backgroundColor: '#fff',
        fontFamily: 'Helvetica',
        fontSize: 11,
        paddingTop: 30,
        paddingLeft: 50,
        paddingRight: 50,
        lineHeight: 1.5,
        flexDirection: 'column',
    },
    logo: {
        width: 84,
        height: 70,
        marginLeft: 'auto',
        marginRight: 'auto'
    }
});

const PdfDocument = ({ invoicedata }) => {
    return (
        <Document>
            <Page size="A4" style={styles.page} >
                <Image style={styles.logo} src={logo} />
                <InvoiceTitle title={'Invoice'} />
                <InvoiceNo invoice={invoicedata} />
                <BillTo invoice={invoicedata} />
                <InvoiceItemsTable invoice={invoicedata} />
                <InvoiceThankYouMsg />
            </Page>
        </Document>
    );
}

export default PdfDocument;

Step 6 - Invoice Heading Component

Create InvoiceTitle.js within the generateInvoice folder and add the following code in it:

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

const styles = StyleSheet.create({
    titleContainer: {
        marginTop: 24,
    },
    reportTitle: {
        color: '#3778C2',
        letterSpacing: 4,
        fontSize: 25,
        textAlign: 'center',
        textTransform: 'uppercase',
    }
});

const InvoiceTitle = ({ title }) => (
    <View style={styles.titleContainer}>
        <Text style={styles.reportTitle}>{title}</Text>
    </View>
);

export default InvoiceTitle;

Step 7 - Invoice Number Component

Create InvoiceNo.js within the generateInvoice folder and add the following code:

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

const styles = StyleSheet.create({
    invoiceNoContainer: {
        flexDirection: 'row',
        marginTop: 36,
        justifyContent: 'flex-end'
    },
    invoiceDateContainer: {
        flexDirection: 'row',
        justifyContent: 'flex-end'
    },
    invoiceDate: {
        fontSize: 12,
        fontStyle: 'bold',
    },
    label: {
        width: 60
    }
});

const InvoiceNo = ({ invoice }) => (
    <Fragment>
        <View style={styles.invoiceNoContainer}>
            <Text style={styles.label}>Invoice No:</Text>
            <Text style={styles.invoiceDate}>{invoice.invoice_no}</Text>
        </View >
        <View style={styles.invoiceDateContainer}>
            <Text style={styles.label}>Date: </Text>
            <Text >{invoice.trans_date}</Text>
        </View >
    </Fragment>
);

export default InvoiceNo;

Step 8 - Invoice Client Component

Create BillTo.js within the generateInvoice folder and add the following code:

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

const styles = StyleSheet.create({
    headerContainer: {
        marginTop: 36,
        justifyContent: 'flex-start',
        width: '50%'
    },
    billTo: {
        marginTop: 20,
        paddingBottom: 3,
        fontFamily: 'Helvetica-Oblique'        
    },
});

const BillTo = ({ invoice }) => (
    <View style={styles.headerContainer}>
        <Text style={styles.billTo}>Bill To:</Text>
        <Text>{invoice.fullname}</Text>
        <Text>{invoice.address}</Text>
        <Text>{invoice.phone}</Text>
        <Text>{invoice.email}</Text>
    </View>
);

export default BillTo;

Step 9 - Create Invoice Items Table

Invoice items table component <InvoiceItemsTable /> is a combined component. It will be made up of <InvoiceTableHeader />, <InvoiceTableRow /> and <InvoiceTableFooter> components.

Create BillTo.js within generateInvoice folder and add the following code:

import React from 'react';
import { View, StyleSheet } from '@react-pdf/renderer';
import InvoiceTableHeader from './InvoiceTableHeader';
import InvoiceTableRow from './InvoiceTableRow';
import InvoiceTableFooter from './InvoiceTableFooter';

const styles = StyleSheet.create({
    tableContainer: {
        flexDirection: 'row',
        flexWrap: 'wrap',
        marginTop: 24,
        borderWidth: 1,
        borderColor: '#3778C2',
    },
});

const InvoiceItemsTable = ({ invoice }) => (
    <View style={styles.tableContainer}>
        <InvoiceTableHeader />
        <InvoiceTableRow items={invoice.items} />
        <InvoiceTableFooter items={invoice.items} />
    </View>
);

export default InvoiceItemsTable;

Step 10 - Invoice Items Table Header Component

Create InvoiceTableHeader.js within generateInvoice folder and add the following code. This component does not require any props to be passed.

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

const borderColor = '#3778C2'
const styles = StyleSheet.create({
    container: {
        flexDirection: 'row',
        borderBottomColor: '#3778C2',
        backgroundColor: '#3778C2',
        color: '#fff',
        borderBottomWidth: 1,
        alignItems: 'center',
        height: 24,
        textAlign: 'center',
        fontStyle: 'bold',
        flexGrow: 1,
    },
    description: {
        width: '60%',
        borderRightColor: borderColor,
        borderRightWidth: 1,
    },
    qty: {
        width: '10%',
        borderRightColor: borderColor,
        borderRightWidth: 1,
    },
    rate: {
        width: '15%',
        borderRightColor: borderColor,
        borderRightWidth: 1,
    },
    amount: {
        width: '15%'
    },
});

const InvoiceTableHeader = () => (
    <View style={styles.container}>
        <Text style={styles.description}>Item Description</Text>
        <Text style={styles.qty}>Qty</Text>
        <Text style={styles.rate}>Price</Text>
        <Text style={styles.amount}>Amount</Text>
    </View>
);

export default InvoiceTableHeader;

Step 11 - Invoice Items Data Rows Component

Create InvoiceTableRow.js within generateInvoice folder. We need to pass the invoice line items data as a props to this component and using map() function, we will iterate through the line items to create individual table rows.

This below code for InvoiceTableRow.js

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

const borderColor = '#3778C2'
const styles = StyleSheet.create({
    row: {
        flexDirection: 'row',
        borderBottomColor: '#3778C2',
        borderBottomWidth: 1,
        alignItems: 'center',
        height: 24,
        fontStyle: 'bold',
    },
    description: {
        width: '60%',
        textAlign: 'left',
        borderRightColor: borderColor,
        borderRightWidth: 1,
        paddingLeft: 8,
    },
    qty: {
        width: '10%',
        borderRightColor: borderColor,
        borderRightWidth: 1,
        textAlign: 'right',
        paddingRight: 8,
    },
    rate: {
        width: '15%',
        borderRightColor: borderColor,
        borderRightWidth: 1,
        textAlign: 'right',
        paddingRight: 8,
    },
    amount: {
        width: '15%',
        textAlign: 'right',
        paddingRight: 8,
    },
});

const InvoiceTableRow = ({ items }) => {
    const rows = items.map(item =>
        <View style={styles.row} key={item.sno.toString()}>
            <Text style={styles.description}>{item.desc}</Text>
            <Text style={styles.qty}>{item.qty}</Text>
            <Text style={styles.rate}>{item.rate}</Text>
            <Text style={styles.amount}>{(item.qty * item.rate).toFixed(2)}</Text>
        </View>
    );
    return (<Fragment>{rows}</Fragment>)
};

export default InvoiceTableRow;

Step 12 - Invoice Table Footer Component

This component is used for displaying the totals of the line item amounts.

Create InvoiceTableFooter.js within generateInvoice folder and add the following code:

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

const borderColor = '#3778C2'
const styles = StyleSheet.create({
    row: {
        flexDirection: 'row',
        borderBottomColor: '#3778C2',
        borderBottomWidth: 1,
        alignItems: 'center',
        height: 24,
        fontSize: 12,
        fontStyle: 'bold',
    },
    description: {
        width: '85%',
        textAlign: 'right',
        borderRightColor: borderColor,
        borderRightWidth: 1,
        paddingRight: 8,
    },
    total: {
        width: '15%',
        textAlign: 'right',
        paddingRight: 8,
    },
});

const InvoiceTableFooter = ({ items }) => {
    const total = items.map(item => item.qty * item.rate)
        .reduce((accumulator, currentValue) => accumulator + currentValue, 0)
    return (
        <View style={styles.row}>
            <Text style={styles.description}>TOTAL</Text>
            <Text style={styles.total}>{Number.parseFloat(total).toFixed(2)}</Text>
        </View>
    )
};

export default InvoiceTableFooter;

Step 13 - Thank You Message Component

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

Create InvoiceThankYouMsg.js within generateInvoice folder and add the following code:

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

const styles = StyleSheet.create({
    titleContainer: {
        marginTop: 12
    },
    reportTitle: {
        fontSize: 12,
        textAlign: 'center',
        textTransform: 'uppercase',
    }
});

const InvoiceThankYouMsg = () => (
    <View style={styles.titleContainer}>
        <Text style={styles.reportTitle}>*** Thank You ***</Text>
    </View>
);

export default InvoiceThankYouMsg;

Step 14 - Update App.js File

Replace the contents of App.js file with the following contents:

import { PDFDownloadLink, PDFViewer } from '@react-pdf/renderer';
import './App.css';
import PdfDocument from './components/generateInvoice/Invoice';
import InvoiceData from './jsonData/InvoiceData';

function App() {
  const fileName = "Invoice.pdf";

  return (
    <div className="App">
      <PDFViewer width={800} height={500} showToolbar={false}>
        <PdfDocument invoicedata={InvoiceData} />
      </PDFViewer>

      <div className='download-link'>
        <PDFDownloadLink
          document={<PdfDocument invoicedata={InvoiceData} />}
          fileName={fileName}
        >
          {({ blob, url, loading, error }) =>
            loading ? "Loading..." : "Download Invoice"
          }
        </PDFDownloadLink>
      </div>
    </div>
  );
}

export default App;

The PDFViewer component has the showToolbar property with Boolean type, by default it is true. It is used to render the toolbar. Render toolbar is supported on Chrome, Edge and Safari.

Step 15 - Update App.css File

Replace the App.css file with the following styles:

.App {
  text-align: center;
}
.download-link {
  font-size: large;
  font-weight: bold;
}

Folder Structure

The project structure looks as shown in the image below,

folder-structure

Output

react-pdf-output

Downloaded invoice pdf looks likes the image below,

downloaded-invoice-pdf

Summary

In this article, we learned about the process of creating a dynamic PDF invoice using react-pdf and viewing it on a browser using PDFViewer. We also learned about some basic components of react-pdf. We also generated dummy JSON data using JSON Generator

Thanks for reading!!!