#HOWTO: Preview PDF files with React

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:

{
  "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:

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):

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:

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

 

 

 

Leave a comment

Your email address will not be published. Required fields are marked *