How to Generate Custom PDF, Using React and React-PDF.
Abdulrasaq Jamiu Adewuyi
Posted on April 25, 2023
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.
PDF Invoice design
The image below shows the screenshot of a sample PDF invoice we will design for this project.
Page in action
The image below shows a preview of this project. Each invoice sample has download
, print
, and share
buttons.
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
andPdfCard.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.
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
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;
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>
);
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>
);
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>
);
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>
);
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>
))
);
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>
);
Once all components and pages are updated, we should see our PDF displayed on the screen as below.
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
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;
We should see the webpage below:
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>
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>
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>
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.
Posted on April 25, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.