Uploading files is a common feature in most of the web applications. Whether we are building an invoice system, document management application, form, or admin dashboard, we need to let users upload their files and preview them before uploading.
In this tutorial, we will build a simple application with a PDF upload and preview feature using React and pdf.js. We will display a pdf thumbnail so the user can see its first page as a preview, and we will also add the option to remove the uploaded pdf.
1. Create a React Project
To create a new React project, run:
npm create vite@latest pdf-preview -- --template react
cd pdf-preview
npm install
Start the application with:
npm run dev
2. Install PDF.js
Now, we need a pdf library to handle the reading and rendering of pdf pages. We will use the pdf.js package for this. You can install pdf.js with this:
npm install pdfjs-dist
3. Create the Upload Component
Now, create a file named PdfUpload.jsx, and add this; it sets up the input field for uploading the pdf file.
import { useState } from "react";
export default function PdfUpload() {
const [file, setFile] = useState(null);
const handleFileChange = (event) => {
const selectedFile = event.target.files?.[0];
if (selectedFile) {
setFile(selectedFile);
}
};
return (
<div>
<input
type="file"
accept="application/pdf"
onChange={handleFileChange}
/>
{file && <p>{file.name}</p>}
</div>
);
}4. Generate the PDF Preview
Now, we can create the pdf render using the PDF.js package. First, import the package.
import * as pdfjsLib from "pdfjs-dist";
Next, configure the pdf.js worker.
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.mjs",
import.meta.url
).toString();
Now, we can read and render the selected pdf first page.
const data = await file.arrayBuffer();
const pdf = await pdfjsLib.getDocument({ data }).promise;
const page = await pdf.getPage(1);
Next, create a viewport to render the pdf page.
5. Convert the Page into an Image
When the pdf page is rendered into the canvas, we can convert the canvas to an image so we can display it.
const image = canvas.toDataURL("image/png");const [preview, setPreview] = useState(null);
setPreview(image);
{preview && (
<img
src={preview}
alt="PDF preview"
className="max-h-60 w-auto rounded border"
/>
)}6. Add Loading and Validation
The pdf rendering can take a little time, especially if the files are larger. So, we can add a loading state for the pdf rendering. We can also add some validations for the files, like only upload pdf type file and the file sizes smaller than 10mb.
const [loading, setLoading] = useState(false);
if (selectedFile.type !== "application/pdf") {
setError("Please select a PDF file.");
return;
}
if (selectedFile.size > 10 * 1024 * 1024) {
setError("PDF must be smaller than 10 MB.");
return;
}7. Add a Remove Button
We can also add a button to remove the uploaded pdf file.
const removeFile = () => {
setFile(null);
setPreview(null);
setError("");
};<button
onClick={removeFile}
className="mt-3 text-sm text-red-600"
>
Remove
</button>
8. Complete PDF Upload Code
Here is the full code of the PdfUpload.jsx component.
import { useEffect, useState } from "react";
import * as pdfjsLib from "pdfjs-dist";
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.mjs",
import.meta.url
).toString();
export default function PdfUpload() {
const [file, setFile] = useState(null);
const [preview, setPreview] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const handleFileChange = (event) => {
const selectedFile = event.target.files?.[0];
if (!selectedFile) return;
setError("");
setPreview(null);
if (selectedFile.type !== "application/pdf") {
setError("Please select a PDF file.");
return;
}
if (selectedFile.size > 10 * 1024 * 1024) {
setError("PDF must be smaller than 10 MB.");
return;
}
setFile(selectedFile);
};
useEffect(() => {
if (!file) return;
let cancelled = false;
const createPreview = async () => {
setLoading(true);
setError("");
try {
const data = await file.arrayBuffer();
const pdf = await pdfjsLib.getDocument({ data }).promise;
const page = await pdf.getPage(1);
const viewport = page.getViewport({
scale: 0.5,
});
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (!context) {
throw new Error("Could not create canvas context.");
}
canvas.width = viewport.width;
canvas.height = viewport.height;
await page.render({
canvasContext: context,
viewport,
}).promise;
if (!cancelled) {
setPreview(canvas.toDataURL("image/png"));
}
} catch {
if (!cancelled) {
setError("Unable to preview this PDF.");
setPreview(null);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
createPreview();
return () => {
cancelled = true;
};
}, [file]);
const removeFile = () => {
setFile(null);
setPreview(null);
setError("");
};
return (
<div className="mx-auto mt-20 flex w-full max-w-md flex-col items-center rounded-xl border bg-white p-6 text-center">
<h2 className="text-lg font-semibold">
Upload PDF
</h2>
<p className="mt-1 text-sm text-gray-500">
Select a PDF to preview its first page.
</p>
<label className="mt-5 flex w-full cursor-pointer justify-center rounded-lg border-2 border-dashed p-6 text-sm text-gray-600">
Choose PDF file
<input
type="file"
accept="application/pdf"
onChange={handleFileChange}
className="hidden"
/>
</label>
{error && (
<p className="mt-3 text-sm text-red-600">
{error}
</p>
)}
{loading && (
<p className="mt-4 text-sm text-gray-500">
Creating preview...
</p>
)}
{file && !loading && (
<div className="mt-5 w-full rounded-lg border bg-gray-50 p-4">
<div className="flex flex-col items-center">
{preview && (
<img
src={preview}
alt="PDF preview"
className="max-h-60 w-auto rounded border bg-white"
/>
)}
<p className="mt-2 max-w-full truncate text-sm font-medium">
{file.name}
</p>
<p className="mt-1 text-xs text-gray-500">
{(file.size / 1024 / 1024).toFixed(2)} MB
</p>
<button
onClick={removeFile}
className="mt-3 text-sm text-red-600"
>
Remove
</button>
</div>
</div>
)}
</div>
);
}9. Use the Component in Your App
To use our component, import the component into App.jsx.
import PdfUpload from "./PdfUpload";
function App() {
return (
<main className="min-h-screen bg-gray-100 p-8">
<PdfUpload />
</main>
);
}
export default App;
To run the app
npm run dev
We have checked how to implement a simple pdf upload and preview feature in our React app. Upload a file, read and open the file with PDF.js, then render and convert the first page to an image and display the preview. The next step is to handle the option to upload the pdf file to a backend. The preview can be generated and handled completely in the browser, without waiting for the server upload, allowing the users to see their document immediately. You can also add other features to our component, like drag-and-drop uploads, upload progress, api integration, multiple-page previews, and other more advanced document handling features.
To read more about How to Handle Errors with Error Boundaries in React, refer to our blog How to Handle Errors with Error Boundaries in React.