David Yakin
David Yakin

Reputation: 11

PDFtron & react Error: Two instances of WebViewer were created on the same HTML element. Please create a new element for each instance of WebViewer

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?

Upvotes: 1

Views: 5947

Answers (4)

Amin Noura
Amin Noura

Reputation: 317

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;

Upvotes: 0

singwithaashish
singwithaashish

Reputation: 85

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>
);

Upvotes: 0

Gaurav Sharma
Gaurav Sharma

Reputation: 1

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>
  );
}

Upvotes: 0

aweJason
aweJason

Reputation: 21

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.

Upvotes: 2

Related Questions