1

I am trying to show in the app that I built in React a PDF file using PDFtron and encounter the following error: Two instances of WebViewer were created on the same HTML element. Please create a new element for each instance of WebViewer.

my code is:

import { url } from "../../../../config.json";
import React, { useState, useEffect, useRef } from "react";
import { getProject } from "../../../../services/projectService";
import { useParams } from "react-router-dom";
import WebViewer from "@pdftron/webviewer";
import { getCurrentUser } from "../../../../services/userService";
import { Link, Redirect } from "react-router-dom";
import { deleteImage } from "../../../../services/projectService";

const MyContracts = () => {
const [project, setProject] = useState({});
const [counter, setCounter] = useState(0);
const [files, setFiles] = useState([]);
const { id } = useParams();
// const viewerDiv = useRef();
const user = getCurrentUser();
const [viewerUrl, setViewerUrl] = useState(`${url}/files/testing.pdf`);
const viewer = document.getElementById("viewer");
    
useEffect(() => {
getProject(id)
.then(res => {
setProject(res.data);
setFiles(res.data.files.contracts);
})
.catch(error => console.log(error.message));
}, []);
    
useEffect(() => {
if (files.length > 0) {
WebViewer(
{
path: `${url}/lib`,
initialDoc: `${url}/files/testing.pdf`,
fullAPI: true, 
},
viewer
).then(async instance => {
const { docViewer } = instance;
docViewer.getDocument(viewerUrl);
});
}
}, [files, viewerUrl]);
if (!user) return <Redirect to="/private-area/sign-in" />;
if (user && user.isAdmin | (user._id === project.userID))
return (
<div className="container">
</div>
{/********** PDF VIEWER ************/}
<div className="web-viewer" id="viewer"></div>
{/* <div className="web-viewer" ref={viewerDiv} id="viewer"></div> */}
    
{/********** PDF Gallery ************/}
{files !== undefined && (
<>
<h2 className="text-rtl h3Title mt-2">בחר קובץ</h2>
<select
id="select"
className="col-12 text-rtl px-0"
onChange={e => setViewerUrl(e.target.value)}>
{files.map((file, index) => (
<option value={`${url}${file.url}`} key={index}>
{file.name}
</option>
))}
</select>
</>
)}
</div>
);
};
    
export default MyContracts;

What am I doing wrong and how can I fix it?

David Yakin
  • 11
  • 1
  • 2
  • I'm trying to load a new document, but because the public folder is on the server and not on the client side it throws me the error that it can not fulfill the promise and therefore the functions of the instance will not work – David Yakin Jun 29 '21 at 18:50

4 Answers4

2

I see that you are trying to load multiple instances of WebViewer:

useEffect(() => {
        if (files.length > 0) {
            WebViewer(
                {
                    path: `${url}/lib`,
                    initialDoc: `${url}/files/testing.pdf`,
                    fullAPI: true,
                },
                viewer
            ).then(async instance => {
                const { docViewer } = instance;
                docViewer.getDocument(viewerUrl);
            });
        }
    }, [files, viewerUrl]);

Webviewer cannot be instantiated more than once in the same HTML element. If you need a completely different instance, you can hide or remove the HTML element and create a new one to hold the new instance.

That being said, if you just need to load another document, I would recommend using the loadDocument API. You can read more about it here as well.

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
aweJason
  • 21
  • 1
0

i was searching this for last 3 days and finally i found a solution fot it. the code that worked for me was this(using Next,js) //urlcarrier is url link changing when i click on another pdf (i had lots of pdf links mapped)

 import { useEffect, useRef, useState } from 'react';
export default function Pdftron({ urlCarrier }) {
const viewer = useRef(null);
const [instance, setInstance] = useState(null);
useEffect(() => {
console.log('Creating WebViewer instance with urlCarrier:', urlCarrier);
import('@pdftron/webviewer')
.then(({ default: WebViewer }) => {
WebViewer(
{
path: '/webviewer',
initialDoc: urlCarrier,
},
viewer.current
)
.then((i) => {
console.log('WebViewer instance created:', i);
setInstance(i);
const { docViewer } = i;
// you can now call WebViewer APIs here...});
});
}, []);
useEffect(() => {
console.log('urlCarrier changed:', urlCarrier);
if (instance) {
console.log('Loading new document:', urlCarrier);
instance.UI.loadDocument(urlCarrier);
}
}, [urlCarrier, instance]);
return (<div className='MyComponent'>
<div className='webviewer' ref={viewer} style={{ height: '100vh' }}>
</div></div>
  );
}
0

To those of you coming here in the future. Just remove the strict mode from index.js for a temporary fix. for example:

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // <React.StrictMode>
    <App />
  // </React.StrictMode>
);
0

Anytime the files variable change, your code create a new instance of webviewer and it brings up an error. one solution is to set a flag and stop the useEffect functionality if the instance already exist.

const [vw, setVW] = useState();

and then when your instance Created, you can setVW

       WebViewer({
            path: '/webviewer/lib',
            licenseKey: 'license',
            initialDoc: 'test.pdf'
        }, viewer.current)
            .then(instance => {
                setVW(instance);

finally at the start of the useEffect you can check if the instance exist and prevent it to go further.

if (vw) return;
Amin Noura
  • 289
  • 2
  • 12