Two instances of WebViewer were created on the same HTML element ( React App)

PDF.js Express Version

Detailed description of issue
Hi Guys, I’m trying to embed this pdf viewer in a React App, but i’m getting this error when I try to change the path of the pdf inside the useEffect(), with a dynamic variable passed through props. The initial document on the first click is getting rendered correctly, but then if I try to click on another one I have this crash. I have tried to use instance.loadDocument(lib/pdf/${filePath}) but I’m getting an error: “pdf header error”. What shall I do here? Thanks for any help I will get :slight_smile:

Expected behaviour
The pdf should change according to the filepath passed wihout any errors

Does your issue happen with every document, or just one?
Every document

Link to document

Code snippet
const App = ({filePath}) => {

const viewer = useRef(null);

useEffect(() => {

WebViewer(

  {

    path: 'lib',

    initialDoc: `lib/pdf/${filePath}`,

  },

  viewer.current

).then((instance) => {

  const { docViewer, Annotations } = instance;

  const annotManager = docViewer.getAnnotationManager();

  setInstance(instance)

       

  docViewer.on('documentLoaded', () => {

    const rectangleAnnot = new Annotations.RectangleAnnotation();

    rectangleAnnot.PageNumber = 1;

   

    rectangleAnnot.X = 100;

    rectangleAnnot.Y = 150;

    rectangleAnnot.Width = 200;

    rectangleAnnot.Height = 50;

    rectangleAnnot.Author = annotManager.getCurrentUser();

    annotManager.addAnnotation(rectangleAnnot);



    annotManager.redrawAnnotation(rectangleAnnot);

  });

});

}, [filePath]);

return (

<div className="App">

  <div className="header">React sample</div>

  <div className="webviewer" ref={viewer}></div>

</div>

);

};

export default App;

Hello, I’m Ron, an automated tech support bot :robot:

While you wait for one of our customer support representatives to get back to you, please check out some of these documentation pages:

Hey there,

You are remounting webviewer every time filePath changes which is bad practice and will make for a very slow application. I would recommend refactoring your code to something like this:

const MyComponent = () => {

  const viewer = useRef();
  const instance = useRef();

  useEffect(() => {
    WebViewer({
      path: 'lib',
      initialDoc: `lib/pdf/${filePath}`,
    }, viewer.current).then(inst => {
      instance.current = inst
    })
  }, [])

  // load the doc when filePath changes
  useEffect(() => {
    if (instance.current) {
      instance.current.loadDocument(`lib/pdf/${filePath}`)
    }
  }, [filePath])

  return (
    <div className="App">
      <div className="header">React sample</div>
      <div className="webviewer" ref={viewer}></div>
    </div>
  )
}

Thanks!
Logan

1 Like

Thank you Logan! Very appreciated :slight_smile:

1 Like

Hi Logan,

I have a problem with loading pdf file from parent props to child props. If i pass directly from parent props it will have TypeError: Object{…} is not a function. However if i pass through child state i will got an empty display.

Child Component:

import React, { Component, createElement, useState, useRef, useEffect } from "react";
import { WebViewer } from "@pdftron/pdfjs-express";
import "../ui/ReactPdfJs.css";

const PDFExpressCom = ({ getPath }) => {
    const viewer = useRef(null);
    const [currentPath, setCurrentPath] = useState(getPath);
    alert("out 1:   " + currentPath);
    alert("out 2:   " + getPath);

    // if using a class, equivalent of componentDidMount
    useEffect(() => {
        if (getPath) {
            setCurrentPath(getPath);
            alert("inside if1:   " + currentPath);
        }
    }, [currentPath, getPath]);

    useEffect(() => {
        if (getPath) {
            setCurrentPath(getPath);
            alert("inside if2:   " + currentPath);
        }
        if (currentPath) {
            alert("inside if3:   " + currentPath);
            WebViewer(
                {
                    path: "/resources/public/webviewer/lib",
                    initialDoc: currentPath
                },
                viewer.current
            ).then(instance => {
                alert("inside if3:   " + currentPath);
                const { docViewer, Annotations } = instance;
                const annotManager = docViewer.getAnnotationManager();

                docViewer.on("documentLoaded", () => {
                    const rectangleAnnot = new Annotations.RectangleAnnotation();
                    rectangleAnnot.PageNumber = 1;
                    // values are in page coordinates with (0, 0) in the top left
                    rectangleAnnot.X = 100;
                    rectangleAnnot.Y = 150;
                    rectangleAnnot.Width = 200;
                    rectangleAnnot.Height = 50;
                    rectangleAnnot.Author = annotManager.getCurrentUser();

                    annotManager.addAnnotation(rectangleAnnot);
                    // need to draw the annotation otherwise it won't show up until the page is refreshed
                    annotManager.redrawAnnotation(rectangleAnnot);
                });
            });
            alert("inside if4:   " + currentPath);
        }
    }, [getPath]);

    return (
        <div className="App">
            <div className="header">Digital Document</div>
            <div className="webviewer" ref={viewer}></div>
        </div>
    );
};

export default PDFExpressCom;

Parent Component:

import React, { useState, useEffect, createElement } from "react";
import PDFExpressCom from "./components/PDFExpressCom";
import "./ui/ReactPdfJs.css";

const ReactPdfJs = ({ DocFileURL }) => {
    const [path, setPath] = useState(DocFileURL.value);

    useEffect(() => {
        if (DocFileURL.status === "available") {
            setPath(DocFileURL.value);
        }
    }, [DocFileURL]);

    return <PDFExpressCom getPath={path} />;
};

export default ReactPdfJs;

Hi there,

You are remounting webviewer every single time path changes which is probably not what you want to do. Instead, use the loadDocument API to load the new path.

So your child component could look something like this instead:

const PDFExpressCom = (path) => {

  const instance = useRef();

  useEffect(() => {
     WebViewer({
         initialDoc: path
      }).then(i => {
         instance.current = i;
     })
  }, [])

  useEffect(() => {
     if(instance.current) {
         instance.current.loadDocument(path)
     } 
  }, [path])

  return (...)
}
1 Like

thanks logan, I solve this issue with the same approach, and initialDoc path should not be empty or undefined.

import React,{useRef,useEffect} from "react";
import WebViewer from '@pdftron/webviewer'

const Whitepaper = () => {

    const viewer = useRef();
    const instance = useRef();
  
    useEffect(() => {
        if (typeof window !== "undefined") {
            WebViewer({
                path: 'lib',
                initialDoc: "files/whitePaper.pdf",
            }, viewer.current).then(inst => {
                instance.current = inst
            })
        }
    }, [])
  
    // load the doc when filePath changes
    useEffect(() => {
        if (typeof window !== "undefined") {
      if (instance.current) {
        instance.current.loadDocument("lib/files/whitePaper.pdf")
      }
    }
    }, [])
  
    return (
      <div className="App">
        <div className="header">React sample</div>
        <div className="webviewer" ref={viewer}></div>
      </div>
    )
}

export default Whitepaper;

ReferenceError: window is not defined :S

Hi Logan.

What if there is no initial doc? Specifically, I load a pdf document as soon as there is a response (with file buffer) for a request. How can I fix the 2 instance issue in this case?

Any help is appreciated!

Please find below the code:

export default function Viewer({
  bookId,
  readingSession,
  ...
}: {
  bookId: string;
  readingSession: { lastViewedPage: number } | null;
  ...
}) {
  const viewer = useRef(null);
  const instance = useRef();
  ...

  const { status, data: bookBuffer } = useQuery(
    [FETCH_BOOK_CONTENT_KEY, bookId],
    () => readBook(bookId),
    {
      staleTime: 1000 * 60 * 60,
    }
  );

  useEffect(() => {
    if (status === "success") {
      WebViewer(
        {
          path: "/webviewer/lib",
          licenseKey: process.env.NEXT_PUBLIC_PDFJS_KEY,
        },
        viewer.current
      ).then((inst: any) => {
        instance.current = inst;

        const blob = new Blob([bookBuffer], {
          type: "application/pdf,
        });
        inst.UI.loadDocument(blob);

        // Set the language
        inst.UI.setLanguage(language);

        // Disable elements
        const elements = [
          "downloadButton",
          "printButton",
          "languageButton",
          "coverLayoutButton",
          "contextMenuPopup",
        ];
        inst.UI.disableElements(elements);

        const { documentViewer } = inst.Core;
        documentViewer.addEventListener("documentLoaded", () => {
          documentViewer.setCurrentPage(readingSession?.lastViewedPage || 1);
        });

        documentViewer.addEventListener(
          "pageNumberUpdated",
          (pageNumber: number) => {
            currentPage = pageNumber;
          }
        );
      });
    }
  }, [status, bookId]);

  return (
    ...
  );
}