How to create PDF in React App
In many contemporary applications, there is a growing demand for your application able to generate PDF files from data. Your tasks may be crafting reports, legal agreements, or official certificates, there frequently arises a need for the generation of PDF documents. This aligns seamlessly with the immediate objectives of my ongoing project, where the primary task involves exporting data in the PDF file format.
I'm utilizing a well-known pdf-renderer pdf-renderer, library in my React app to generate PDF documents with small afford. It provides a straightforward way to create and download PDF files from my React app.
Getting started
To get started with react-pdf install the package from the command line,
yarn add @react-pdf/renderer
or
npm install @react-pdf/renderer
or
pnpm install @react-pdf/renderer
The basic thing to render a pdf is knowing these important components that pdf-render provides us.
Document
: It is the root of the PDF, like a<html>
tag.Page
: This component represents a single page inside our documents, but we can use multiple inside theDocument
Text
: This component for displaying text, supports nesting of links or other textsView
: The most fundamental component for building a UI, is the design to be nested inside other views, which you can use as a<div>
tag.Image
: A component that can be used for displaying images, keep in mind that you should not miss out on the source object.
Other components like PDFDownloadLink
& BlobProvider
will be useful too if you want to generate pdf on the fly.
Now create your first PDF.
import React from 'react';
import { Page, Text, View, Document, StyleSheet } from '@react-pdf/renderer';
const styles = StyleSheet.create({
page: {
flexDirection: 'column',
backgroundColor: '#F0F0F0'
},
container: {
margin: 10,
padding: 10,
},
text: {
fontSize: 16,
fontWeight: 'bold',
color: 'blue',
}
});
const MyDocument = () => (
<Document>
<Page size="A4" style={styles.page}> // provide the size of your page as you need
<View style={styles.container}>
<Text style={styles.text}>Simple PDF Example</Text>
</View>
<View style={styles.container}>
<Text>How would you like modify.</Text>
</View>
</Page>
</Document>
);
export default MyDocument;
Rendering your first PDF on DOM.
import React from 'react';
import ReactDOM from 'react-dom';
import { PDFViewer } from '@react-pdf/renderer';
const App = () => (
<PDFViewer>
<MyDocument />
</PDFViewer>
);
ReactDOM.render(<App />, document.getElementById('root'));
Elevating Your Content
Often we might need to make a table out of the data we got. You can follow the following code to do that if the provided data or response data are as follows.
const tableData = [
{
id: 1,
name: "Yasmine Kihn",
gender: "female",
phone: "8354650298",
},
{
id: 2,
name: "Moriah Pollich",
gender: "female",
phone: "7854389123",
},
{
id: 3,
name: "Bernie Goodwin",
gender: "male",
phone: "9756893422",
},
];
Here I created a PDFTable
custom component and passed the data to it so that it could be reusable.
import { Document, Page, View, StyleSheet, Text } from "@react-pdf/renderer";
import PDFTable from "./PDFTable";
const styles = StyleSheet.create({
page: {
paddingTop: 35,
paddingBottom: 48,
paddingHorizontal: 48,
boxSizing: "border-box",
},
heading: {
fontSize: 20,
fontWeight: "bold",
marginBottom: 10,
textAlign: "center",
},
});
const MyPDF = () => {
return (
<Document>
<Page size="A4" style={styles.page}>
<View>
<Text style={styles.heading}>My Enhanced PDF</Text>
<PDFTable
tableHeaders={[
{ title: "Name", value: "name" },
{ title: "Gender", value: "gender" },
{ title: "Phone", value: "phone" },
]}
data={tableData}
heading="Employee Details"
/>
</View>
</Page>
</Document>
);
};
export default MyPDF;
Now see what the PDFTable looks like, to handle typescript I have created props types, you can skip this part if you are not using TS.
type tableHeaders<T> = {
title: string;
value: keyof T;
render?: (item: any, rowData?: T, index?: number) => JSX.Element;
};
interface PDFTable<
T extends {
[value: string | number]: any;
}
> {
data?: T[] | null;
heading: string;
tableHeaders?: tableHeaders<T>[];
extraStyle?: boolean;
}
Since our style can vary from our parents, unlike on our other component here I used styles inside the component, it works the same as working outside the component.
const PDFTable = <
T extends {
[key: string]: any;
}
>({
data,
heading,
tableHeaders,
extraStyle,
}: PDFTable<T>) => {
const styles = StyleSheet.create({
table: {
width: "100%",
marginBottom: 20,
borderRadius: 4,
marginTop: 10,
border: extraStyle ? "" : "1px solid #e0e0e0",
},
row: {
flexDirection: "row",
borderBottomWidth: 1,
borderBottomColor: "#e0e0e0",
alignItems: "center",
minHeight: 30,
flexWrap: "wrap",
":not(:last-child)": {
borderBottom: "none",
},
},
rowHead: {
flexDirection: "row",
},
cell: {
flex: 1,
padding: 8,
fontSize: 11,
color: "#424242",
textTransform: "capitalize",
alignItems: "center",
},
headerCell: {
backgroundColor: "#DAF7A6",
fontWeight: "bold",
overflow: "hidden",
whiteSpace: "nowrap",
alignItems: "center",
},
snCell: {
flex: 0.4,
padding: "8px 4px",
fontSize: 11,
color: "#424242",
backgroundColor: "#DAF7A6",
fontWeight: 700,
flexWrap: "wrap",
},
snRow: {
flex: 0.4,
padding: "8px 4px",
fontSize: 11,
color: "#424242",
fontWeight: "bold",
},
innerHeading: {
fontSize: 16,
fontWeight: 700,
marginBottom: 12,
marginTop: 12,
},
});
return (
<View wrap={true}>
<Text style={styles.innerHeading}>{heading}</Text>
<View style={styles.table}>
{/* Table Headers */}
<View style={[styles.rowHead, { justifyContent: "space-between" }]}>
<Text style={styles.snCell}>S.N</Text>
{tableHeaders?.map((thead, headerIndex) => (
<Text key={thead.title} style={[styles.cell, styles.headerCell]}>
{thead.title}
</Text>
))}
</View>
{/* Table content */}
{Array.isArray(data) &&
data?.map((item, dataIndex) => (
<View key={dataIndex}>
<View style={styles.row}>
<Text style={styles.snRow}>{dataIndex + 1}.</Text>
{tableHeaders?.map((header, headerIndex) => (
<Text style={styles.cell} key={headerIndex}>
{header?.render
? header?.render?.(item?.[header.value], item, dataIndex)
: item[header.value] ?? ""}
</Text>
))}
</View>
</View>
))}
</View>
</View>
);
};
export default PDFTable;
Don't forget to render it on PDFViewer.
The results look like:
If I have to mention a couple of things on the above code they are:
wrap
: this prop is taken by our View of thewrap
works in the way that if the space is not available because of other content above it forces them to always render in a new page.PDFViewer
: It provides an Iframe of PDF viewer for client-side generated documents.
Fonts
One of the best things about using react-pdf is that it gives Font
a module that enables you to load fonts from different sources. In my case, I have used Poppins fonts as follows.
import { StyleSheet, Font } from '@react-pdf/renderer'
import poppins from "@/assets/fonts/Poppins-Regular.ttf";
const FONT_FAMILY = "Poppins";
Font.register({
src: poppins,
family: FONT_FAMILY,
});
Font.registerHyphenationCallback((word) => [word]);
const styles = StyleSheet.create({
paragraph: {
fontFamily: FONT_FAMILY
}
Using react-query
In most of our recent apps, we no longer use useEffect to fetch data. Also, in most cases, we are prefetching data and we send data as a prop to the PDF component, like:
<PDFViewer height={800} width={800} showToolbar>
<MyPDF data={data} />
</PDFViewer>
If you come across using useQuery inside your PDF renderer file wrap with it, and you're good to go.
import { QueryClient, QueryClientProvider } from 'react-query';
const queryClient = new QueryClient();
<PDFViewer height={800} width={800} showToolbar>
<QueryClientProvider client={queryClient}>
<MyPDF />
</QueryClientProvider>
</PDFViewer>
Wrapping up
react-pdf stands compared to another pdf library out there for react web app or mobile app(react-native). I am using it for my projects. There are drawbacks too. If I have to point out there is styling limitations for example there is overflow
provides only hidden
property and other do not support, display
only supports flex
and none
. Hope there will be necessary changes to version 4.
I hope you all have some idea about react-pdf.
Let’s connect with another blog article.
Until then, Happy Coding!
Don’t forget to ping me on Linkedin.