0

I'm trying to show a PDF in my page by using "react-pdf". I am now struggling with making it responsive. I am now using this custom hook to get the size of the contentRect:

import { useEffect, useState, useCallback } from "react"; interface Size { width: number; height: number; } const useResizeObserver = (targetRef: HTMLElement | null): Size => { const [size, setSize] = useState<Size>({ width: 0, height: 0 }); const handleResize = useCallback((entries: ResizeObserverEntry[]) => { const [entry] = entries; if (entry) { setSize({ width: entry.contentRect.width, height: entry.contentRect.height, }); } }, []); useEffect(() => { if (!targetRef) return; const resizeObserver = new ResizeObserver(handleResize); resizeObserver.observe(targetRef); return () => { resizeObserver.disconnect(); }; }, [targetRef, handleResize]); return size; }; export default useResizeObserver; 

This is my PDFViewer.tsx that is used in the page.tsx:

"use client"; import React, { useRef, useState } from "react"; import { Document, Page } from "react-pdf"; import "react-pdf/dist/esm/Page/AnnotationLayer.css"; import { pdfjs } from "react-pdf"; import "react-pdf/dist/Page/TextLayer.css"; import type { PDFDocumentProxy } from 'pdfjs-dist'; import useResizeObserver from "@/hooks/useResizeObserver"; pdfjs.GlobalWorkerOptions.workerSrc = new URL( "pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url ).toString(); const PDFViewer: React.FC = () => { const [numPages, setNumPages] = useState<number>(0); const [pageNumber, setPageNumber] = useState<number>(1); const containerRef = useRef<HTMLDivElement | null>(null); const { width: pdfWidth, height: pdfHeight } = useResizeObserver(containerRef.current); const pdfUrl = "/docs/menu.pdf"; function onDocumentLoadSuccess({ numPages: nextNumPages }: PDFDocumentProxy): void { setNumPages(nextNumPages); } return ( <div className="flex flex-row left-2 mt-4 justify-center items-center w-full h-auto px-4"> <div className="flex flex-1 font-generalRegular justify-right text-right text-primary text-6xl px-4"> {" < "} </div> <div id="containerRef" ref={containerRef} className="flex flex-8 flex-col font-generalRegular justify-center text-center text-primary text-sm px-4"> <p>Menu</p> <p>Page {pageNumber} of {numPages}</p> <p>Width: {pdfWidth}</p> <Document file={pdfUrl} onLoadSuccess={onDocumentLoadSuccess}> <Page pageNumber={pageNumber} width={pdfWidth}/> </Document> </div> <div className="flex flex-1 font-generalRegular justify-left text-left text-primary text-6xl px-4"> {" > "} </div> </div> ); }; export default PDFViewer; 

As you might notice, I've added a text element to print the width. Yet, it never changes. Even when I constantly resize the browser window. Can anybody tell me what went wrong here?

Thank you.

2
  • @KJ It's always 595 for me. Commented Mar 2 at 15:45
  • But still, shouldn't the width be changed when I resized the windows? But this one didn't. Commented Mar 2 at 16:18

1 Answer 1

1

When you call useResizeObserver use pass it the value of containerRef.current, which is undefined. After the component renders, and calls useEffect, the value is still null, which skips the observer's init due to if (!targetRef) return;.

const { width: pdfWidth, height: pdfHeight } = useResizeObserver(containerRef.current); 

Instead change the hook to accept the entire ref object, and after the render the current value would update:

const useResizeObserver = (targetRef: RefObject<HTMLElement | null>): Size => { const [size, setSize] = useState<Size>({ width: 0, height: 0 }); useEffect(() => { const handleResize = (entries: ResizeObserverEntry[]) => { const [entry] = entries; if (entry) { setSize({ width: entry.contentRect.width, height: entry.contentRect.height, }); } }; const resizeObserver = new ResizeObserver(handleResize); resizeObserver.observe(targetRef.current); return () => { resizeObserver.disconnect(); }; }, []); return size; }; 

Calling it:

const { width: pdfWidth, height: pdfHeight } = useResizeObserver(containerRef); 

Running example (without TS):

const { useState, useEffect, useRef } = React; const useResizeObserver = targetRef => { const [size, setSize] = useState({ width: 0, height: 0 }); useEffect(() => { const handleResize = entries => { const [entry] = entries; if (entry) { setSize({ width: entry.contentRect.width, height: entry.contentRect.height, }); } }; const resizeObserver = new ResizeObserver(handleResize); resizeObserver.observe(targetRef.current); return () => { resizeObserver.disconnect(); }; }, []); return size; }; const Demo = () => { const ref = useRef(); const size = useResizeObserver(ref); console.log(size); return <div className="target" ref={ref} /> } ReactDOM .createRoot(root) .render(<Demo />);
.target { height: 50vh; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script> <div id="root"></div>


However, in certain cases, for example components that are added / removed dynamically, the ref might not be ready in time. In those cases the hook can have a state that holds the ref. The hook returns a function to set the ref, and when the ref updates, the useEffect is called with the updated ref:

const useResizeObserver = () => { const [targetRef, setTargetRef] = useState<HTMLElement | null>(null); const [size, setSize] = useState<Size>({ width: 0, height: 0 }); const handleResize = useCallback((entries: ResizeObserverEntry[]) => { const [entry] = entries; if (entry) { setSize({ width: entry.contentRect.width, height: entry.contentRect.height, }); } }, []); useEffect(() => { if (!targetRef) return; const resizeObserver = new ResizeObserver(handleResize); resizeObserver.observe(targetRef); return () => { resizeObserver.disconnect(); }; }, [targetRef, handleResize]); return { setTargetRef, size }; }; 

Usage:

const PDFViewer: React.FC = () => { const [numPages, setNumPages] = useState<number>(0); const [pageNumber, setPageNumber] = useState<number>(1); const { setTargetRef, size: { width: pdfWidth, height: pdfHeight }} = useResizeObserver(); /* unrelated code **/ return ( /* unrelated code **/ <div id="containerRef" ref={setTargetRef} className="classed"> /* unrelated code **/ ); }; 

Running example (without TS):

const { useState, useEffect, useCallback } = React; const useResizeObserver = () => { const [targetRef, setTargetRef] = useState(null); const [size, setSize] = useState({ width: 0, height: 0 }); const handleResize = useCallback(entries => { const [entry] = entries; if (entry) { setSize({ width: entry.contentRect.width, height: entry.contentRect.height, }); } }, []); useEffect(() => { if (!targetRef) return; const resizeObserver = new ResizeObserver(handleResize); resizeObserver.observe(targetRef); return () => { resizeObserver.disconnect(); }; }, [targetRef, handleResize]); return { setTargetRef, size }; }; const Demo = () => { const { setTargetRef, size } = useResizeObserver(); console.log(size); return <div className="target" ref={setTargetRef} /> } ReactDOM .createRoot(root) .render(<Demo />);
.target { height: 50vh; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script> <div id="root"></div>

Sign up to request clarification or add additional context in comments.

3 Comments

I've fixed the code because the original one doesn't compile. But anyway, both still don't work. I still get 595 always.
Both solutions work. I've added 2 working snippets (without TS). Note that you don't need to add a ref (not ref.current) as a dependency usually. The return type fix was needed.
That's very strange. Because it didn't change at all in my project. Is it because the react pdf's Document and Page elements somehow made it unchangeable? If so then I'm at an impasse because I need it to make the pdf responsive based on the size of the contentRect.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.