Categories
JavaScript React

Add a PDF Reader to a React App with react-pdf-viewer

Spread the love

Adding React viewers is a common requirement for web apps. With React, there’s the react-pdf-viewer package that lets us add PDF viewers to React apps easily.

In this article, we’ll look at how to use it to add a PDF viewer to our React app.

Installation

We can install it by running:

npm install @phuocng/react-pdf-viewer

Basic Usage

After installing it, we can use it as follows:

import React from "react";
import Viewer, { Worker } from "@phuocng/react-pdf-viewer";

import "@phuocng/react-pdf-viewer/cjs/react-pdf-viewer.css";

export default function App() {
  return (
    <div className="App">
      <Worker workerUrl="https://unpkg.com/pdfjs-dist@2.2.228/build/pdf.worker.min.js">
        <div style={{ height: "750px" }}>
          <Viewer fileUrl="dummy.pdf" />
        </div>
      </Worker>
    </div>
  );
}

In the code above, we included the CSS file that comes with the package, and the Viewer for opening the PDF viewer and the Worker component for loading the PDF specified in the fileUrl prop as in the background.

We have to include the workerUrl prop with that URL so that the worker in that location is run.

Then we’ll get a PDF viewer that has zoom in and out controls, page navigation, document properties, and download options.

In a Create React App project, static files like PDFs should be in the public folder so that it can be loaded. Also, PDFs have to be in the same domain as the React app so that we won’t get CORS errors.

Options

There’re options for customization. We can change the layout of the sidebar, toolbar, and replace default controls with React components of our choice.

Layout options available include:

  • isSidebarOpened – boolean to indicate whether we want the sidebar to open or not
  • main – the main part of the viewer (a Slot component object)
  • toolbar – toolbar part (a RenderToolbar component object)
  • sidebarSlot object to define the sidebar of the viewer

For instance, we can write the following code to add a sidebar to display pages and a layout component do define a layout for our PDF viewer as follows:

import React from "react";
import Viewer, {
  Worker,
} from "@phuocng/react-pdf-viewer";

import "@phuocng/react-pdf-viewer/cjs/react-pdf-viewer.css";


export default function App() {
  const renderToolbar = (toolbarSlot) => {
    return (
      <div
        style={{
          alignItems: 'center',
          display: 'flex',
          width: '100%',
        }}
      >
        <div
          style={{
            alignItems: 'center',
            display: 'flex',
          }}
        >
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.searchPopover}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.previousPageButton}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.currentPageInput} / {toolbarSlot.numPages}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.nextPageButton}
          </div>
        </div>
        <div
          style={{
            alignItems: 'center',
            display: 'flex',
            flexGrow: 1,
            flexShrink: 1,
            justifyContent: 'center',
          }}
        >
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.zoomOutButton}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.zoomPopover}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.zoomInButton}
          </div>
        </div>
        <div
          style={{
            alignItems: 'center',
            display: 'flex',
            marginLeft: 'auto',
          }}
        >
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.fullScreenButton}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.openFileButton}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.downloadButton}
          </div>
          <div style={{ padding: '0 5px' }}>
            {toolbarSlot.moreActionsPopover}
          </div>
        </div>
      </div>
    );
  };

  const layout = (
    isSidebarOpened,
    main,
    toolbar,
    sidebar
  ) => {
    return (
      <div
        style={{
          border: '1px solid rgba(0, 0, 0, .3)',
          display: 'grid',
          gridTemplateAreas: "'toolbar toolbar' 'sidebar main'",
          gridTemplateColumns: '30% 1fr',
          gridTemplateRows: '40px calc(100% - 40px)',
          height: '100%',
          overflow: 'hidden',
          width: '100%',
        }}
      >
        <div
          style={{
            alignItems: 'center',
            backgroundColor: '#EEE',
            borderBottom: '1px solid rgba(0, 0, 0, .1)',
            display: 'flex',
            gridArea: 'toolbar',
            justifyContent: 'center',
            padding: '4px',
          }}
        >
          {toolbar(renderToolbar)}
        </div>
        <div
          style={{
            borderRight: '1px solid rgba(0, 0, 0, 0.2)',
            display: 'flex',
            gridArea: 'sidebar',
            justifyContent: 'center',
          }}
        >
          {sidebar.children}
        </div>
        <div
          {...main.attrs}
          style={Object.assign({}, {
            gridArea: 'main',
            overflow: 'scroll',
          }, main.attrs.style)}
        >
          {main.children}
        </div>
      </div>
    );
  };

  return (
    <Worker workerUrl="https://unpkg.com/pdfjs-dist@2.2.228/build/pdf.worker.min.js">
      <Viewer
        fileUrl='dummy.pdf'
        layout={layout}
      />
    </Worker>
  );
}

The default layout is the following:

┌───────────┬───────────┐
│ toolbar   │ toolbar   │
├───────────┼───────────┤
│ sidebar   │ main      │
└───────────┴───────────┘

In the code above, we reference those parts in the places we wish to place in the layout component. The children attribute has the parts of each component. attrs has the default props of each component, which we can change.

The renderToolbar function is a higher-order component that takes a toolbarSlot prop, which has the parts of the toolbar and we can place them accordingly according to our needs. In the example above, we put them in different divs and added our own styles to each div.

The grid above is a CSS grid, so we can modify the layout as we please and it’ll will in all modern browsers.

Those pats are passed in as props in the layout component. So we can reference them directly from the parameters of layout.

Conclusion

The react-pdf-viewer package is a very useful PDF viewer that’s designed with both performance and usability in mind. The default layout and controls are already very good. Performance comes from loading PDFs in the background with a web worker.

It’s also very customizable, we can define a layout component that has toolbar, sidebar and main as props and then we can customize them as we wish.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

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