“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,
Output
Downloaded invoice pdf looks likes the image below,
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!!!