In this article, we will learn how to generate with by taking inputs from users. Some of the use cases include generating , , , based on data received, etc. dynamic PDFs React invoices certificates resumes reports To enable PDF downloading, we will use package which provides useful components like , , , , , , etc. react-pdf Document Page View Image Text PDFDownloadLink PDFViewer Let's check each component: This tag represents the PDF document itself and must be the root of our PDF. Document: It represents a single page inside the PDF documents and should always be rendered inside the Document component only. Page: This component helps to build UI for the PDFs. It can be nested inside other views. View: It's used for displaying network or local (Node only) JPG or PNG images in PDFs. Image: Used to display text in PDFs. It also supports the nesting of other Text components. Text: It allows you to generate and download PDF documents. PDFDownloadLink: It is used for rendering client-side generated documents. PDFViewer: Required Installations Create react app using the following cmd: pdf-invoice npx create-react-app react-pdf-invoice After the successful creation of the app, use the following commands to go to a directory and start the project - cd react-pdf-invoice npm start Command to install in react app: react-pdf Using npm npm install @react-pdf/renderer --save Using yarn yarn add @react-pdf/renderer Folder structure: Creating Invoice Form Since our PDF is dynamic in nature, there will be options to add/delete items, change the price/quantity of the products, computation of total amount based on the items mentioned. As a result, we need to take inputs from users and show the data accordingly. src > components > createInvoice > InvoiceForm.js import React, { useState } from 'react'; import InvoicePDF from '../getPDF/InvoicePDF'; import { PDFDownloadLink } from '@react-pdf/renderer'; import './styles.css'; const InvoiceForm = () => { // state for storing info about user creating Invoice const [billFrom, setBillFrom] = useState({ name: '', address: '', invoiceNumber: '', }) // state for capturing info of person who needs to pay const [client, setClient] = useState({ clientName: '', clientAddress: '', }) // items description containing name, price and quantity const [items, setItems] = useState([{ name: '', quantity: 0, price: 0}]); const handleBillFromData = (e) => { e.preventDefault(); const {name, value} = e.target; setBillFrom({ ...billFrom, [name] : value }) } const handleClientData = (e) => { e.preventDefault(); const {name, value} = e.target; setClient({ ...client, [name] : value }) } const handleItemChange = (e, index, field, value) => { e.preventDefault(); const updatedItems = [...items]; updatedItems[index][field] = value; // updating the item field (using index) according to user's input setItems(updatedItems); // updating the items array }; const handleAddItem = () => { setItems([...items, { name: '', quantity: 0, price: 0}]); // adding new item to items array }; const handleRemoveItem = (index) => { const updatedItems = [...items]; updatedItems.splice(index, 1); // removing the selected item setItems(updatedItems); // updating the items array }; // to compute the items' total amount const total = () => { return items.map(({price, quantity}) => price * quantity).reduce((acc, currValue) => acc + currValue, 0); } return ( <div className="invoice"> <div> <h1 className='title'>Invoice</h1> <div className='firstRow'> <div className='inputName'> <label>Invoice Number:</label> <input name="invoiceNumber" className="input" type="text" value={billFrom.invoiceNumber} onChange={handleBillFromData} /> </div> </div> <div className='firstRow'> <div className='inputName'> <label>Name:</label> <input name="name" className="input" type="text" value={billFrom.name} onChange={handleBillFromData} /> </div> <div className='inputName'> <label>Address:</label> <textarea name="address" className="textarea" type="text" value={billFrom.address} onChange={handleBillFromData} /> </div> </div> <hr/> <h2>Bill To:</h2> <div className='firstRow'> <div className='inputName'> <label>Client Name:</label> <input name="clientName" className="input" type="text" value={client.clientName} onChange={handleClientData} /> </div> <div className='inputName'> <label>Address:</label> <textarea name="clientAddress" className="textarea" type="text" value={client.clientAddress} onChange={handleClientData} /> </div> </div> <h2 className='title'>Add Details</h2> <div className='subTitleSection'> <h2 className='subTitle item'>Item</h2> <h2 className='subTitle quantity'>Quantity</h2> <h2 className='subTitle price'>Price</h2> <h2 className='subTitle action'>Amount</h2> </div> {items?.map((item, index) => ( <div key={index} className='firstRow'> <input className="input item" type="text" value={item.name} onChange={(e) => handleItemChange(e, index, 'name', e.target.value)} placeholder="Item Name" /> <input className="input quantity" type="number" value={item.quantity} onChange={(e) => handleItemChange(e, index, 'quantity', e.target.value)} placeholder="Quantity" /> <input className="input price" type="number" value={item.price} onChange={(e) => handleItemChange(e, index, 'price', e.target.value)} placeholder="Price" /> <p className='amount'>$ {item.quantity * item.price}</p> <button className='button' onClick={() => handleRemoveItem(index)}>-</button> </div> ))} <button className='button' onClick={handleAddItem}>+</button> <hr/> <div className='total'> <p>Total:</p> <p>{total()}</p> </div> <hr/> <PDFDownloadLink document={<InvoicePDF billFrom={billFrom} client={client} total={total} items={items} />} fileName={"Invoice.pdf"} > {({ blob, url, loading, error }) => loading ? "Loading..." : <button className='button'>Print Invoice</button> } </PDFDownloadLink> </div> </div> ); }; export default InvoiceForm; Storing User's Info const handleBillFromData = (e) => { e.preventDefault(); const {name, value} = e.target; setBillFrom({ ...billFrom, [name] : value }) } In the above-mentioned code, we are storing the information (name, invoice number, address) of the person who is creating an Invoice. Storing Client's Info const handleClientData = (e) => { e.preventDefault(); const {name, value} = e.target; setClient({ ...client, [name] : value }) } In the above code block, the function will update the Client's information (name, address) who needs to pay the total amount based on the user's input. Create/Update/Delete operations on Items const handleItemChange = (e, index, field, value) => { e.preventDefault(); const updatedItems = [...items]; updatedItems[index][field] = value; // updating the item field (using index) according to user's input setItems(updatedItems); // updating the items array }; const handleAddItem = () => { setItems([...items, { name: '', quantity: 0, price: 0}]); // adding new item to items array }; const handleRemoveItem = (index) => { const updatedItems = [...items]; updatedItems.splice(index, 1); // removing the selected item setItems(updatedItems); // updating the items array }; will add a new item having , , as input fields whenever user clicks on button. handleAddItem() itemName quantity price + will remove the selected item when user clicks on button. handleRemoveItem() - will update the selected item by grabbing index of that particular item with the values (input by the user). handleItemChange() Props of PDFDownloadLink <PDFDownloadLink document={<InvoicePDF billFrom={billFrom} client={client} total={total} items={items} />} fileName={"Invoice.pdf"} > {({ blob, url, loading, error }) => loading ? "Loading..." : <button className='button'>Print Invoice</button> } </PDFDownloadLink> The above-mentioned code snippet is responsible for generating a PDF document using all the inputs taken by the user. : To implement PDF document functionality document : Name of the PDF once downloaded filename : Tag for adding styling style Adding Styling in Invoice Form src > components > createInvoice > styles.css .invoice { display: flex; padding: 10px; margin: 20px; border-radius: 12px; justify-content: center; width: 1200px; box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; } .title { font-size: 30px; } .firstRow { display: flex; justify-content: space-between; flex-grow: 1; } .inputName { display: flex; flex-direction: column; } .total { display: flex; justify-content: space-between; font-size: 20px; } .total > p { align-items: center; justify-content: center; font-weight: 700; } .inputName > label { font-size: 20px; font-weight: 600; margin-right: 5px; text-align: left; } .input, .textarea{ font-size: 20px; border-radius: 5px; padding-left: 10px; margin: 10px 0px; } .subTitleSection { background: bisque; display: flex; justify-content: space-between; border-radius: 12px; flex-grow: 1; padding-right: 15px; } .subTitle { font-size: 20px; font-weight: 700; margin: 10px 10px; text-align: left; } .item { width: 50%; } .quantity { width: 15%; } .price { width: 15%; } .action { width: 10%; } .amount { font-size: 18px; font-weight: 700; } .remove { border-radius: 12px; height: 20px; width: 20px; } .button { background-color: #405cf5; border-radius: 6px; border-width: 0; box-sizing: border-box; color: #fff; cursor: pointer; font-size: 18px; height: 44px; padding: 0 25px; } Invoice Form UI Invoice with multiple items: Generating PDF Documents Based on Invoice Data Once we get the required data from the user's end, we feed the data to the component responsible for generating the PDF document. In our case, InvoicePDF is that component. src > components > getPDF > InvoicePDF.js import React from 'react'; import { Page, Text, View, Document, StyleSheet } from '@react-pdf/renderer'; import ItemsTable from './ItemsTable'; const styles = StyleSheet.create({ page: { flexDirection: 'column', padding: 20, }, name: { flexDirection: 'column', justifyContent: 'flex-start', fontSize: 22, marginBottom: 5 }, invoiceNumber: { flexDirection: 'column', justifyContent: 'flex-start', }, section: { margin: 10, padding: 10, flexGrow: 1, }, header: { fontSize: 24, marginBottom: 10, textAlign: 'center' }, label: { fontSize: 12, marginBottom: 5, }, input: { marginBottom: 10, paddingBottom: 5, }, client: { borderTopWidth: 1, marginTop: 20, marginBottom: 10 }, }); const InvoicePDF = ({ billFrom, client, total, items }) => { // destructuring props return ( <Document> <Page size="A4" style={styles.page}> <View> <Text style={styles.header}>Invoice Form</Text> <View> <Text style={styles.name}>{billFrom.name}</Text> </View> <View style={styles.invoiceNumber}> <Text style={styles.label}>INVOICE NO.</Text> <Text style={styles.input}>{billFrom.invoiceNumber}</Text> </View> <View style={styles.invoiceNumber}> <Text style={styles.label}>ADDRESS</Text> <Text style={styles.input}>{billFrom.address}</Text> </View> <View style={styles.client}></View> <Text style={styles.label}>BILL TO</Text> <View> <Text style={styles.name}>{client.clientName}</Text> </View> <View style={styles.invoiceNumber}> <Text style={styles.label}>CLIENT ADDRESS</Text> <Text style={styles.input}>{client.clientAddress}</Text> </View> <ItemsTable items={items} total={total} /> </View> </Page> </Document> ); }; export default InvoicePDF; Create an Items Table listing all the products src > components > getPDF > ItemsTable.js import React from 'react' import { Text, View, StyleSheet } from '@react-pdf/renderer'; const styles = StyleSheet.create({ row: { flexDirection: 'row', borderBottomWidth: 1, backgroundColor: '#D3D3D3', borderTopColor: 'black', borderTopWidth: 1, borderBottomColor: 'black', fontStyle: 'bold', alignItems: 'center', height: 22, }, quantity: { width: '10%', borderRightWidth: 1, textAlign: 'right', borderRightColor: '#000000', paddingRight: 10, }, description: { width: '60%', borderRightColor: '#000000', borderRightWidth: 1, textAlign: 'left', paddingLeft: 10, }, price: { width: '15%', borderRightColor: '#000000', borderRightWidth: 1, textAlign: 'right', paddingRight: 10, }, amount: { width: '15%', textAlign: 'right', paddingRight: 10, }, total: { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', borderBottomColor: 'black', borderBottomWidth: 1, } }) const ItemsTable = ({ items, total }) => { // destructuring props return ( <View> <View style={styles.row}> <Text style={styles.description}>Item Description</Text> <Text style={styles.quantity}>Qty</Text> <Text style={styles.price}>Price</Text> <Text style={styles.amount}>Amount</Text> </View> { items.map((item, index) => ( <View key={index} style={styles.row}> <Text style={styles.description}>{item.name}</Text> <Text style={styles.quantity}>{item.quantity}</Text> <Text style={styles.price}>{item.price}</Text> <Text style={styles.amount}>$ {item.quantity * item.price}</Text> </View> ))} <View style={styles.total}> <Text>Total: </Text> <Text>$ {total()} </Text> </View> </View> ) } export default ItemsTable; Updated App.js src > App.js import './App.css'; import InvoiceForm from './components/createInvoice/InvoiceForm'; function App() { return ( <div className="App"> <InvoiceForm /> </div> ); } export default App; Output On clicking , a PDF document named gets downloaded with the following structure: Print Invoice Invoice.pdf Conclusion With this article, we got to know how to download a dynamic PDF document with the help of based on the user's inputs. To gain more insights, play around with the other components. react-pdf References https://react-pdf.org/ Also published here.