React components are chunks of isolated code that can easily be shared across your entire UI, and even across multiple projects. This post will go over how to create a PDF viewing component that you can use in your projects. We will also cover an abstraction technique that you can use to help future-proof your code.

linkStep 1 - Install Dependencies

First of all, we need an environment to create our component in. For this example we will be using create-react-app to generate a project. Navigate to an empty directory and run the following command:

npm i create-react-app --save-dev

linkStep 2 - Create the Project

Use the following commands to generate your React project:

npx create-react-app my-app
cd my-app

This will install React and any other dependencies you need. Now you can start your local server by running:

npm run start

Navigate to http://localhost:3000/ and you should see the default create-react-app welcome screen.

linkStep 3 - Project Setup

Next we need to set up our project. First, let’s start by clearing the templates that create-react-app provides. Navigate to src/App.js and delete all the code inside the render function, except the outer div. The file should now contain:

import React, { Component } from 'react';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">

      </div>
    );
  }
}

export default App;

We can also delete any other unused code. Delete src/logo.svg and App.test.js, and also replace all the css in App.css with the following snippet:

html, body, #root, .App {
  width: 100%;
  height: 100%;
}

Now we need to create our PDF viewing component. Let’s create the file src/components/PDFViewer/PDFViewer.js and start by writing a basic React component.

import React from 'react';

export default class PDFViewer extends React.Component {

  render() {
    return (
      <div id='viewer' style={{ width: '100%', height: '100%' }}>
        Hello world!
      </div>
    )
  }
}

Now, back in App.js, lets import this component and render it. App.js should now look like this:

import React, { Component } from 'react';
import './App.css';
import PDFViewer from './components/PDFViewer/PDFViewer';

class App extends Component {
  render() {
    return (
      <div className="App">
        <PDFViewer />
      </div>
    );
  }
}

export default App;

If you take a look at http://localhost:3000 , you should see a little “Hello world!” on a white background.

linkStep 4 - Defining Our Backend

Now it’s time to start adding some functionality!

Instead of hardcoding PDF functionality right inside our React component, we are going to use a more abstracted approach and pass a class that implements a set of functions to the component. We will call these classes 'backends', and they will help the component render the PDF. Using this approach makes our React component even more reusable and future proof.

Let’s create our first backend that renders a PDF using pdf.js. Create the file src/backends/pdfjs.js and export a class that contains an init function.

export default class PDFJs {
  init = () => {
    
  }
}

Most PDF libraries require you to pass a DOM node to render the PDF inside. We will also need to know which PDF to render in the first place. Let’s make the init function accept both of these things as parameters.

export default class PDFJs {
  init = (source, element) => {
    
  }
}

Before we start implementing PDF rendering, let’s get this backend hooked up to our component. In App.js, import the pdfjs.js backend and pass it as a prop to our PDFViewer component. We’re also going to need a PDF source, so lets pass that as well. App.js should now look something like this:

import React, { Component } from 'react';
import './App.css';
import PDFViewer from './components/PDFViewer/PDFViewer';
import PDFJSBackend from './backends/pdfjs';

class App extends Component {
  render() {
    return (
      <div className="App">
        <PDFViewer 
          backend={PDFJSBackend}
          src='/myPDF.pdf'
        />
      </div>
    );
  }
}

export default App;

Now in our PDFViewer component, lets implement the backends init function. First we start by creating an instance of the backend and storing it to the component.

import React from 'react';

export default class PDFViewer extends React.Component {
  constructor(props) {
    super(props);
    this.viewerRef = React.createRef();
    this.backend = new props.backend();
  }

  render() {
    return (
      <div ref={this.viewerRef} id='viewer' style={{ width: '100%', height: '100%' }}>

      </div>
    )
  }
}

Since we need reference to a DOM node inside our init function, we will call it inside componentDidMount (DOM nodes are guarenteed to be present when componentDidMount is called). We will pass it a reference to the #viewer div (using React refs), as well as the PDF source. We can also remove the "Hello world" from the render function while we're at it. PDFViewer.js should now look like this:

import React from 'react';

export default class PDFViewer extends React.Component {
  constructor(props) {
    super(props);
    this.viewerRef = React.createRef();
    this.backend = new props.backend();
  }

  componentDidMount() {
    const { src } = this.props;
    const element = this.viewerRef.current;

    this.backend.init(src, element);
  }
  

  render() {
    return (
      <div ref={this.viewerRef} id='viewer' style={{ width: '100%', height: '100%' }}>

      </div>
    )
  }
}

The final step is making our init function actually do something. Lets test it out by making it render some text.

export default class PDFJs {
  init = (source, element) => {
    const textNode = document.createElement('p');
    textNode.innerHTML = `Our PDF source will be: ${source}`;

    element.appendChild(textNode);
  }
}

http://localhost:3000 should now display "Our PDF source will be: /myPDF.pdf".

linkStep 5 - Implementing pdf.js

We will start by using the open sourced pdf.js library to render a PDF. Download the library from https://mozilla.github.io/pdf.js/getting_started/#download and extract the contents inside the public folder of our project.

We will also need a PDF file to view. You can download one from here, or use your own. Place it in the public folder as well.

Your overall project structure should now like something like this (your version number of pdf.js may be different - this is okay):

We will implement pdf.js ui by using an iframe to point to its viewer file. In our init function, we will create an iframe and set its source to the pdf.js ui, and we will use query params to tell the UI which PDF to render. Once the iframe is created, we will append it to the DOM element passed into the init function.

src/backends/pdfjs.js should now look like this:

export default class PDFJs {
  init = (source, element) => {
    const iframe = document.createElement('iframe');

    iframe.src = `/pdfjs-1.9.426-dist/web/viewer.html?file=${source}`;
    iframe.width = '100%';
    iframe.height = '100%';

    element.appendChild(iframe);
  }
}

Thats it! If you check out http://localhost:3000, you should see your PDF displayed inside the pdf.js viewer.

Viewing a PDF using pdf.js is fairly easy, but once you want to start annotating or using more advanced features such as real time collab, redaction, or page manipulation, you would have to implement these things yourself.

That's where PDFTron comes in. Our Webviewer library provides all these features out of the box, with zero configuration. Webviewer also has far better render accuracy in many cases.

linkImplementing with Webviewer

Webviewer is PDFTrons Web SDK, which allows you to open PDFs, Office docs, and many other formats right in the browser. You can view a demo here.

Start by getting a free trial key here (if you don't already have one). Once done, we need to download and import the Webviewer dependencies into our project. You can download the files from here. Then, extract the contents into /public

Now import the files by adding the following code in public/index.html right before </head>.

<script src='/WebViewer/lib/webviewer.min.js'></script>

We are now ready to create our Webviewer backend. Create the file src/backends/webviewer.js, and export a class with an init function that accepts a PDF source and a DOM element. (You can probably see where this is going!)

Copy this Webviewer constructor code into the init function, and replace the l parameter with your PDFTron license key:

export default class PDFTron {
  init = (source, element) => {
    new window.PDFTron.WebViewer({
      path: '/WebViewer/lib',
      l: 'YOUR_KEY_HERE',
      initialDoc: source,
    }, element);
  }
}

Now is when all our hard work pays off! To switch our app to use Webviewer instead of pdf.js, we can just pass a different backend to our component. In App.js, replace the pdf.js backend with our new Webviewer backend.

import React, { Component } from 'react';
import './App.css';
import PDFViewer from './components/PDFViewer/PDFViewer';
import PDFJSBackend from './backends/pdfjs';
import WebviewerBackend from './backends/webviewer';

class App extends Component {
  render() {
    return (
      <div className="App">
        <PDFViewer backend={WebviewerBackend} src='/myPDF.pdf' />
      </div>
    );
  }
}

export default App;

And that's it! Our PDFViewer component doesn't require any changes because it already knows to call the init function when the component is mounted.

linkExtending the backends (optional)

Lets say we want to implement a programmatic rotate feature into our component.

We can start by adding a rotate function into our Webviewer backend.

export default class Webviewer {
  init = (source, element) => {
    new window.PDFTron.WebViewer({
      path: '/WebViewer/lib',
      l: 'YOUR_KEY_HERE',
      initialDoc: source,
    }, element);
  }

  rotate = (direction) => {

  }
}

We will also need reference to our Webviewer instance, so lets save this as part of the instance in our init function.

export default class Webviewer {
  init = (source, element) => {
    this.viewer = new window.PDFTron.WebViewer({
      path: '/WebViewer/lib',
      l: 'YOUR_KEY_HERE',
      initialDoc: source,
    }, element);
  }


  rotate = (direction) => {

  }
}

We can now use the Webviewer rotateClockwise and rotateCounterClockwise functions to rotate the current document. Full documentation can be found here.

export default class Webviewer {
  init = (source, element) => {
    this.viewer = new window.PDFTron.WebViewer({
      path: '/WebViewer/lib',
      l: 'YOUR_KEY_HERE',
      initialDoc: source,
    }, element);
  }


  rotate = (direction) => {
    if(direction === 'clockwise') {
      this.viewer.rotateClockwise();
    } else {
      this.viewer.rotateCounterClockwise();
    }
  }
}

Now, we just need to implement this into our PDFViewer component.

import React from 'react';

export default class PDFViewer extends React.Component {

  constructor(props) {
    super(props);
    this.viewerRef = React.createRef();
    this.backend = new props.backend();
  }

  componentDidMount() {
    const { src } = this.props;
    const element = this.viewerRef.current;
    this.backend.init(src, element);
  }

  rotate = (direction) => {
    // check to make sure the backend has this function implemented
    if (this.backend.rotate) {
      this.backend.rotate(direction);
    }
  }

  render() {
    return (
      <div ref={this.viewerRef} id='viewer' style={{ width: '100%', height: '100%' }}>

      </div>
    )
  }
}

That's it! You could now implement a rotate function to your PDFJs backend if you wanted.

To use this rotate function, we can get a reference to our PDFViewer component and call the rotate function directly.

import React, { Component } from 'react';
import './App.css';
import PDFViewer from './components/PDFViewer/PDFViewer';
import PDFJSBackend from './backends/pdfjs';
import WebviewerBackend from './backends/webviewer';

class App extends Component {

  constructor() {
    super();
    this.myViewer = React.createRef();
  }

  onButtonClick = () => {
    this.myViewer.current.rotate('clockwise');
  }

  render() {
    return (
      <div className="App">

        <button onClick={this.onButtonClick}>Rotate Clockwise</button>

        <PDFViewer
          ref={this.myViewer}
          backend={WebviewerBackend}
          src='/myPDF.pdf'
        />

      </div>
    );
  }
}

export default App;

linkConclusion

As you can see, viewing a PDF inside a React app isn't tough using open source software. Once you start needing more advanced features, however, open source software just won't cut it. Thankfully, PDFTron's Webviewer has all this advanced functionality built right in, and it's just as simple to implement.

We also saw how abstracting the functionality allows us to future-proof our code and allow us to easily switch from one viewer to another without having to touch our components.

The full source code for this article can be found here.

You can view a full demo of Webviewer here. Feel free to compare it to the pdf.js viewer.

If you have any questions about implementing Webviewer in your project, please feel to contact us and we will be happy to help!