Some test text!
Web / FAQ / Sharing WebViewer instance across components
Webviewer can easily be instantiated inside a React component, however, the instance
object returned from the WebViewer constructor is limited to the scope of the component its created in:
import {useEffect, useRef} from 'react';
import WebViewer from '@pdftron/webviewer'
const MyComponent = () => {
const viewer = useRef(null);
useEffect(() => {
WebViewer(
{
path: '/webviewer/lib',
initialDoc: 'https://pdftron.s3.amazonaws.com/downloads/pl/demo-annotated.pdf',
},
viewer.current,
).then((instance) => {
// We only have access to the instance here
});
}, []);
return (
<div className="MyComponent">
<div className="webviewer" ref={viewer} style={{height: "100vh"}}></div>
</div>
);
};
In most React apps, you want to separate functionality on a per-component basis. For example, you may want a React component that loads a new document.
One way you could do this is by passing instance
down as a prop to all of your components - but this can get messy and leads to prop drilling. Prop drilling occurs when you pass a prop down through multiple child components, and this pattern should be avoided because it leads to messy, hard to maintain code.
To avoid prop drilling, we can use a powerful feature in React called context.
Context allows you to share data between components without the use of props. This is a great feature that can really clean up your code.
Let's use the context feature to share our WebViewer instance object with all of our components.
The first thing we need to do is create the context itself. This can be done with the createContext
function. This context can live anywhere in your app, but for this example lets place it in src/context/webviewer.js
// src/context/webviewer.js
import React from 'react'
export default WebViewerContext = React.createContext({});
Next, we need to provide our application with this context. You may be familiar with this concept if you have ever used libraries like Apollo, Redux, Chakra, etc.
To set up our provider, we simply wrap our entire app with the provider exported from our context:
// src/App.js
import WebViewerContext from 'src/context/webviewer.js '
export default function App() {
return (
<WebViewerContext.Provider>
{ /* ...Your application code here */}
</WebViewerContext.Provider>
)
}
Now, any components rendered inside this provider have access to the context.
We also have to provide some kind of value to our application. This value can be whatever you want, but for this example we will provide a way to get and set the WebViewer instance.
Expanding on our previous code, we provide set a getter and setter for the instance using the useState
hook:
// src/App.js
import WebViewerContext from 'src/context/webviewer.js'
import { useState } from 'react'
export default function App() {
const [instance, setInstance] = useState();
return (
<WebViewerContext.Provider value={{ instance, setInstance }}>
{ /* ...Your application code here */}
</WebViewerContext.Provider>
)
}
Now, every component in our app has access to both instance
and setInstance
.
The next step is setting the WebViewer instance object after WebViewer has been loaded. To do this, we need to call the setInstance
function provided by our provider.
In our WebViewer component, we can gain access to this function with the useContext
hook:
// src/components/WebViewer.js
import WebViewerContext from 'src/context/webviewer.js'
import { useEffect, useRef, useContext } from 'react';
import WebViewer from '@pdftron/webviewer'
export default function WebViewerComponent() {
// useContext returns whatever "value"
// is provided by our provider we set up above
const { setInstance } = useContext(WebViewerContext);
const viewer = useRef(null);
useEffect(() => {
WebViewer(
{
path: '/webviewer/lib',
initialDoc: '/files/pdftron_about.pdf',
},
viewer.current,
).then((instance) => {
setInstance(instance)
});
}, []);
return (
<div className="MyComponent">
<div className="webviewer" ref={viewer} style={{height: "100vh"}}></div>
</div>
);
}
This code is loading WebViewer and mounting it to the DOM, and then calling the setInstance
function provided from our provider. If everything is set up correctly, the instance
state in App.js
should now be set to the WebViewer instance, which means that our provider is now providing the WebViewer instance object to the rest of the application!
Now that the instance is set in our provider, every component now has access to it. This allows us to call WebViewer APIs without passing any props.
Let's create a LoadDocument
component now using our new context:
// src/components/LoadDocument.js
import WebViewerContext from 'src/context/webviewer.js'
import { useEffect, useRef, useContext } from 'react';
export default function LoadDocument() {
const { instance } = useContext(WebViewerContext)
const load = () => {
instance.UI.loadDocument('http://yourwebsite.com/file.pdf')
}
return (
<button onClick={load}>Load document</button>
)
}
You can see that this component now has access to the WebViewer instance with no props needed!
We can clean up our code even further by creating a custom hook that just returns the instance. This prevents us from having to import the context everywhere we want to use it.
Let's create a custom hook called useInstance
:
// src/hooks/useInstance
import WebViewerContext from 'src/context/webviewer.js'
import { useContext } from 'react';
export default function useInstance() {
const { instance } = useContext(WebViewerContext);
return instance;
}
This hook is very simple, but helps us clean up our code a bit. We no longer need to import WebViewerContext
in all our components.
Let's go back and update our LoadDocument component:
// src/components/LoadDocument.js
import useInstance from 'src/hooks/useInstance'
import { useEffect, useRef } from 'react';
export default function LoadDocument() {
const instance = useInstance();
const load = () => {
instance.UI.loadDocument('http://yourwebsite.com/file.pdf')
}
return (
<button onClick={load}>Load document</button>
)
}
As you can see, leveraging React context to share state across multiple components can really improve your code readability and prevents the need for prop drilling.
Trial setup questions? Ask experts on Discord
Need other help? Contact Support
Pricing or product questions? Contact Sales