In addition to my blog post #HOWTO: Up- and downloading files with React and Spring Boot, I want to show you a simple way to display PDF files in the browser with React.
I'm using create-react-app
to bootstrap the React application. For styling purposes, I've added semantic-ui-react
and semantic-ui-css
. The library for previewing the PDF files is called react-pdf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | { "name": "pdf-preview-react", "version": "0.1.0", "private": true, "dependencies": { "react": "^16.7.0", "react-dom": "^16.7.0", "react-pdf": "^4.0.2", "react-scripts": "2.1.3", "semantic-ui-css": "^2.4.1", "semantic-ui-react": "^0.85.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": [ ">0.2%", "not dead", "not ie <= 11", "not op_mini all" ] } |
The library is capable of displaying PDFs from an URL, a local file or from form input. In addition, you can display specific pages of the PDF document and navigate through every page. For a sample setup, I've used the generated <App>
component and added the following JSX
markup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | render() { const { pageNumber, numPages } = this.state; return ( <Container> <br /> <Header textAlign="center">PDF Preview</Header> <Form> <input type="file" onChange={this.onFileChange}> </input> </Form> <Grid centered columns={2}> <Grid.Column textAlign="center" onClick={this.nextPage}> <Document file={this.state.file} onLoadSuccess={this.onDocumentLoadSuccess} noData={<h4>Please select a file</h4>}> <Page pageNumber={pageNumber} /> </Document> {this.state.file ? <p>Page {pageNumber} of {numPages}</p> : null} </Grid.Column> </Grid> </Container> ); } |
The components Document
and Page
are part of the react-pdf dependency and the rest is part of Semantic UI React. With a file upload, the uploaded file is stored in the state of the <App>
component and rendered within the <Document>
component. To navigate through the file, I've added a click listener on the wrapper class of the <Document>
component and incrementing the current page with every click (or setting to 1 if the end of the document is reached):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | import React from 'react'; import { Container, Header, Grid, Form } from 'semantic-ui-react'; import { Document, Page } from 'react-pdf'; class App extends React.Component { state = { file: null, numPages: 0, pageNumber: 1 } onFileChange = (event) => { this.setState({ file: event.target.files[0] }); } onDocumentLoadSuccess = ({ numPages }) => { this.setState({ numPages }); } nextPage = () => { const currentPageNumber = this.state.pageNumber; let nextPageNumber; if (currentPageNumber + 1 > this.state.numPages) { nextPageNumber = 1; } else { nextPageNumber = currentPageNumber + 1; } this.setState({ pageNumber: nextPageNumber }); } // ... render method as described above } |
To optimize performance and as mentioned in the documentation of the react-pdf getting started guide, you should enable PDF.js worker whenever possible as this will render the PDF file in a separate thread without affecting page performance. The setup for a React project generated with create-react-app
is a little bit different then the plain Webpack setup described in the guide. For an up-to-date worker, I've added the following lines to the index.js
file:
1 2 | import { pdfjs } from 'react-pdf'; pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; |
You can find the whole code base in my GitHub repository and try it out on your machine.
Have fun with previewing PDF files in your browser,
Phil