How to render PDF docs in React with Adobe PDF Embed API (fetching URL).
Before I get to HOW I want to talk about WHY first. I was building a platform that would render PDF docs sourced out of my AWS s3 bucket and I just <Iframe>-ed it on the page which was easy to do and seems to work just fine. Well testing the application I have realized something — if you have a multi-page PDF doc that is being iframed (or <embed>) you won't be able to render it from a mobile device, the mobile browser will render it in an image and will show you only the first page of the document. That’s not gonna work for me and after exploring options I’ve come up with an idea to use Adobe Embed PDF API and here I’ll walk you through the setup for React app.
First of all, we need to create an account and generate a client id key to use it. It is free and upon creating the key it’ll ask you for the domain name it will be used from (side note: It won’t work from any other domain name and even localhost won’t be able to render the doc, although it’ll render the pdf reader so you can tell that it work).
create new credentials here:
https://www.adobe.io/apis/documentcloud/dcsdk/gettingstarted.html
- Add above’s script in ./public/index.html at the bottom of the body tag.
<script src="https://documentcloud.adobe.com/view-sdk/main.js"></script>
2. Create a document in ViewSDKClient.js in ./src/ViewSDKClient.js
3. Let’s fill it in:
class ViewSDKClient {constructor() {this.readyPromise = new Promise((resolve) => {if (window.AdobeDC) {resolve();} else {document.addEventListener("adobe_dc_view_sdk.ready", () => {resolve();});}});this.adobeDCView = undefined;}ready() {return this.readyPromise;}previewFile(divId, viewerConfig, url) {const config = {clientId: "be44a0cd9b6848f9965783cf16356f78",};if (divId) {config.divId = divId;}this.adobeDCView = new window.AdobeDC.View(config);const previewFilePromise = this.adobeDCView.previewFile({content: {location: {url: url,},},metaData: {fileName: "Menu.pdf",id: "6d07d124-ac85-43b3-a867-36930f502ac6",}}, viewerConfig);return previewFilePromise;}previewFileUsingFilePromise(divId, filePromise, fileName) {this.adobeDCView = new window.AdobeDC.View({clientId: "be44a0cd9b6848f9965783cf16356f78",divId,});this.adobeDCView.previewFile({content: {promise: filePromise,},metaData: {fileName: fileName}}, {});}registerSaveApiHandler() {const saveApiHandler = (metaData, content, options) => {console.log(metaData, content, options);return new Promise(resolve => {setTimeout(() => {const response = {code: window.AdobeDC.View.Enum.ApiResponseCode.SUCCESS,data: {metaData: Object.assign(metaData, {updatedAt: new Date().getTime()})},};resolve(response);}, 2000);});};this.adobeDCView.registerCallback(window.AdobeDC.View.Enum.CallbackType.SAVE_API,saveApiHandler,{});}registerEventsHandler() {this.adobeDCView.registerCallback(window.AdobeDC.View.Enum.CallbackType.EVENT_LISTENER,event => {console.log(event);},{enablePDFAnalytics: true,});}}export default ViewSDKClient;
I got this file from their GitHub repo and made a few changes, first of all, there are two places where you need to insert your generated client id.
4. Inside of the component you want to render a pdf you need to make sure you have a link for that available, like if you’re making an API call to get that link.
import React, { useEffect } from 'react';import axios from 'axios';import CircularProgress from '@material-ui/core/CircularProgress';import ViewSDKClient from './ViewSDKClient.js';const RenderMenu = (props) => {const [state, setState] = React.useState({isDataLoaded: false, menuLink: null, hasFile: false});useEffect(() => {axios.get(`${process.env.REACT_APP_BASE_URL}/get_menu`).then(response => setState({isDataLoaded: true, hasFile: response.data.has_file, menuLink: response.data.menu_link})).catch(error => alert(error.message))}, []);const loadPDF = () => {const viewSDKClient = new ViewSDKClient();viewSDKClient.ready().then(() => {viewSDKClient.previewFile("pdf-div", {showAnnotationTools: false, showLeftHandPanel: false, showPageControls: false,showDownloadPDF: false, showPrintPDF: false}, state.menuLink);});}return (<div >{state.isDataLoaded ?<div>{state.hasFile ?<><div id="pdf-div" className="full-window-div" onDocumentLoad={loadPDF()}></div></>:<div><p className='text dashboard' id="no-file">Sorry, no file at this link</p></div>}</div>:<div className='cp'><CircularProgress style={{color: '#ffc107'}} /></div>}</div>);}export default RenderMenu;
let me explain the logic here:
import React, { useEffect } from 'react';
import axios from 'axios';
import CircularProgress from '@material-ui/core/CircularProgress';
import ViewSDKClient from './ViewSDKClient.js';
First, we import all the necessary things, especially ViewSDKClient file that we just created, then I used axios to make API call instead of fetch and React.useEffect instead of ‘ComponentDidMount’ since I’m using the functional component.
const [state, setState] = React.useState({isDataLoaded: false, menuLink: null, hasFile: false});
State to keep track of data after the fetch and re-render components accordingly.
useEffect(() => {
axios.get(`${process.env.REACT_APP_BASE_URL}/get_menu`)
.then(response => setState({isDataLoaded: true, hasFile: response.data.has_file, menuLink: response.data.menu_link}))
.catch(error => alert(error.message))
}, []);
making an API call to fetch data and store it on the state.
const loadPDF = () => {
const viewSDKClient = new ViewSDKClient();
viewSDKClient.ready().then(() => {
viewSDKClient.previewFile("pdf-div", {showAnnotationTools: false, showLeftHandPanel: false, showPageControls: false,
showDownloadPDF: false, showPrintPDF: false}, state.menuLink);
});
}
LoadPDF is a function that is called when <div> with an id of ‘pdf-div’ being loaded, which happens after we get a response back.
return (<div >{state.isDataLoaded ?<div>{state.hasFile ?<><div id="pdf-div" className="full-window-div" onDocumentLoad={loadPDF()}></div></>:<div><p className='text dashboard' id="no-file">Sorry, no file at this link</p></div>}</div>:<div className='cp'><CircularProgress style={{color: '#ffc107'}} /></div>}</div>);}export default RenderMenu;
in our return, we first check if dataIsLoaded, defaults to false in our state, and until we get our promise, we’ll be rendering CircularProgress from the Material-UI library.
Second check if it hasFile, that we get in from the promise when data comes back, is it is we render
<div id="pdf-div" className="full-window-div" onDocumentLoad={loadPDF()}></div>
if not, we’ll render the message that there is no file.
Well finally when we get to load our <div id=”pdf-div”> that will fire up loadPDF function and it’ll attach pdf reader to that div. You might want to play around with the height and width of that div depending on your need.
That’s pretty much all of it, if you have any questions, please leave them in the comments.