A PDF file displays read-only documents with text, images, and links. The PDF files are generally accessed through third-party applications such as Adobe reader, etc, and rendering PDF files on a web application is not supported natively. Hence, we will use external npm packages to accomplish the following functionalities:
- Display the PDF file on the screen.
- Feature to Zoom in and Zoom out of a document on button click.
- Allow users to navigate between the previous and next pages.
1) Install “react-pdf” npm package
The “react-pdf” name package provides utility functions to render a PDF file along with additional features such as pagination and page scale.
npm i react-pdf
// For projects configured with yarn
yarn add react-pdf
2) Enable pdf.js worker to process file content
PDF.js is a javascript library that allows a browser-based application to render PDF files and is importable from “react-pdf” package.
import { pdfjs } from "react-pdf";
To process PDF files pdf.js requires a worker.js file that can be accessed through the global CDNs.
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
3) Access PDF file in React project
There are two approaches to accessing a file inside React JS project:
- Include a PDF file inside /public directory of the project, once included the file can be directly referenced using the file name.
- Specify the URL where the PDF file is hosted which requires bypassing the CORS security.
const url =
"https://cors-anywhere.herokuapp.com/https://ncu.rcnpv.com.tw/Uploads/20131231103232738561744.pdf";
4) Load the PDF document
We declare a React state “totalPages” to maintain the count of total pages in the PDF file. The value for “totalPages” is initially “0” and it’s updated after the document is loaded.
const [totalPages, setTotalPages] = useState(0);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
}
The <Document/> component provides direct access to the specified file for the child components.
import { Document } from "react-pdf";
<Document file="sampleFile.pdf" onLoadSuccess={onDocumentLoadSuccess}>
</Document>
5) Display file using <Page/> component
import { Document, Page, pdfjs } from "react-pdf";
The PDF file is controlled using the following React states:
- pageNumber: Indicates the current page number of the file.
- pageScale: Number that indicates how much the PDF file is Zoomed.
const [pageNumber, setPageNumber] = useState(1);
const [pageScale, setPageScale] = useState(1);
The PDF file must be displayed page by page, hence <Page/> component is used to render an individual page in the document.
<Document file="sampleFile.pdf" onLoadSuccess={onDocumentLoadSuccess}>
<Page pageNumber={pageNumber} scale={pageScale} />
</Document>
6) Zoom In/Zoom out of the PDF file
The document is zoomed in/out by incrementing/decrementing the page scale. Additionally, an upper and lower limit of “pageScale” state is required to ensure that the application doesn’t break.
function handleZoomIn() {
if (pageScale < 3) {
setPageScale(pageScale + 0.2);
}
}
function handleZoomOut() {
if (pageScale > 0.3) {
setPageScale(pageScale - 0.2);
}
}
<div className="button-container">
<button onClick={handleZoomIn} disabled={pageScale >= 3}>
Zoom +
</button>
<button onClick={handleZoomOut} disabled={pageScale <= 0.3}>
Zoom -
</button>
</div>
7) Pagination with next and previous buttons
The page of the document is updated dynamically using “setPageNumber()” method. It is important to limit page numbers between 1 to the total no of pages for ensuring multiple quick button clicks are ignored once the start/end point is reached.
function handleNext() {
if (pageNumber < totalPages) {
setPageNumber(pageNumber + 1);
}
}
function handlePrevious() {
if (pageNumber > 0) {
setPageNumber(pageNumber - 1);
}
}
Disabling buttons when previous and next page actions cannot be performed improves the user experience.
<div className="page-text">
Page {pageNumber} of {totalPages}
</div>
<div className="button-container">
<button onClick={handlePrevious} disabled={pageNumber === 1}>
‹ Previous
</button>
<button onClick={handleNext} disabled={pageNumber === totalPages}>
Next ›
</button>
</div>
Final Solution Code
App.js
import React, { useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";
import "./styles.css";
export default function App() {
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
const [totalPages, setTotalPages] = useState(0);
const [pageNumber, setPageNumber] = useState(1);
const [pageScale, setPageScale] = useState(1);
const url =
"https://cors-anywhere.herokuapp.com/https://ncu.rcnpv.com.tw/Uploads/20131231103232738561744.pdf";
function onDocumentLoadSuccess({ numPages }) {
setTotalPages(numPages);
}
function handleZoomIn() {
if (pageScale < 3) {
setPageScale(pageScale + 0.2);
}
}
function handleZoomOut() {
if (pageScale > 0.3) {
setPageScale(pageScale - 0.2);
}
}
function handleNext() {
if (pageNumber < totalPages) {
setPageNumber(pageNumber + 1);
}
}
function handlePrevious() {
if (pageNumber > 0) {
setPageNumber(pageNumber - 1);
}
}
return (
<div className="App">
<div className="page-container">
<Document file="sampleFile.pdf" onLoadSuccess={onDocumentLoadSuccess}>
<Page pageNumber={pageNumber} scale={pageScale} />
</Document>
</div>
<div className="footer">
<div className="button-container">
<button onClick={handleZoomIn} disabled={pageScale >= 3}>
Zoom +
</button>
<button onClick={handleZoomOut} disabled={pageScale <= 0.3}>
Zoom -
</button>
</div>
<div className="page-text">
Page {pageNumber} of {totalPages}
</div>
<div className="button-container">
<button onClick={handlePrevious} disabled={pageNumber === 1}>
‹ Previous
</button>
<button onClick={handleNext} disabled={pageNumber === totalPages}>
Next ›
</button>
</div>
</div>
</div>
);
}
styles.css
.App {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.page-container {
margin: 20px;
width: 650px;
height: 700px;
overflow-y: scroll;
box-shadow: 0 25px 50px 0 rgba (62, 62, 62, 0.15);
-webkit-box-shadow: 0 25px 50px 0 rgb(62 62 62 / 15%);
}
.footer {
display: flex;
justify-content: space-between;
width: 500px;
}
.button-container {
display: flex;
gap: 8px;
}