wojtekmaj / react-pdf

Display PDFs in your React app as easily as if they were images.
https://projects.wojtekmaj.pl/react-pdf
MIT License
9.34k stars 882 forks source link

the pdf file download multiple times #1800

Closed jiangxiaoqiang closed 4 months ago

jiangxiaoqiang commented 4 months ago

Before you start - checklist

Description

Recnently I found loading the PDF will download multiple times:

image

I have tried to use memo like this:

import React, { useRef, useState } from 'react';
import { Document, Page } from 'react-pdf';
import styles from "./MemoizedPDFPreview.module.css";
import { DocumentCallback, Options, PageCallback } from 'react-pdf/dist/cjs/shared/types';
import { AppState } from '@/redux/types/AppState';
import { useSelector } from 'react-redux';
import { ProjAttribute } from '@/model/proj/config/ProjAttribute';
import { PdfPosition } from '@/model/proj/pdf/PdfPosition';
import { ProjInfo } from '@/model/proj/ProjInfo';
import Highlight from '../feat/highlight/Highlight';
import { PageViewport } from 'pdfjs-dist';
import { readConfig } from '@/config/app/config-reader';

interface PDFPreviewProps {
    curPdfUrl: string;
    projId: string;
    options: Options;
    viewModel: string;
}

const MemoizedPDFPreview: React.FC<PDFPreviewProps> = React.memo(({ 
    curPdfUrl, 
    projId, 
    options,
    viewModel='default' 
}) => {
    const [numPages, setNumPages] = useState<number>();
    let curPage = localStorage.getItem(readConfig("pdfCurPage") + projId);
    const [currentPage, setCurrentPage] = useState<number>(Number(curPage));
    let pdfScaleKey = "pdf:scale:" + projId;
    let cachedScale = Number(localStorage.getItem(pdfScaleKey));
    const [projAttribute, setProjAttribute] = useState<ProjAttribute>({ pdfScale: cachedScale });
    const [curProjInfo, setCurProjInfo] = useState<ProjInfo>();
    const [viewport, setViewport] = useState<PageViewport>();
    const { projAttr, pdfFocus, projInfo } = useSelector((state: AppState) => state.proj);
    const [curPdfPosition, setCurPdfPosition] = useState<PdfPosition[]>();
    const canvasArray = useRef<Array<React.MutableRefObject<HTMLCanvasElement | null>>>([]);

    React.useEffect(() => {
        setCurProjInfo(projInfo);
    }, [projInfo]);

    React.useEffect(() => {
        if (projAttr.pdfScale === 1 && cachedScale) {
            return;
        }
        setProjAttribute(projAttr);
    }, [projAttr, cachedScale]);

    React.useEffect(() => {
        if (pdfFocus && pdfFocus.length > 0) {
            let pageNum = pdfFocus[0].page;
            setCurPdfPosition(pdfFocus);
            localStorage.setItem(readConfig("pdfCurPage") + curProjInfo?.main.project_id, pageNum.toString());
            goPage(pageNum);
            setTimeout(() => {
                setCurPdfPosition([]);
            }, 5000);
        }
    }, [pdfFocus]);

    const updateRefArray = (index: number, element: HTMLCanvasElement | null) => {
        if (element) {
            canvasArray.current[index] = { current: element };
        }
    };

    const handlePageChange = (page: any) => {

    };

    var pageObserve = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
        entries.forEach((item: IntersectionObserverEntry) => {
            if (item.intersectionRatio > 0) {
                let dataPage = item.target.getAttribute('data-page-number');
                setCurrentPage(Number(dataPage));
                if (!dataPage) return;
                localStorage.setItem(readConfig("pdfCurPage") + projId, dataPage.toString())
            }
        })
    }, {
        threshold: 0,
    });

    const goPage = (i: number) => {
        let element = document.querySelectorAll(`.${styles.pdfPage}`);
        if (element && element.length > 0 && i) {
            element[i - 1]!.scrollIntoView({ behavior: 'smooth' });
        }
    }

    const handlePageRenderSuccess = (page: PageCallback) => {
        let elements = document.querySelectorAll(`.${styles.pdfPage}`);
        if (elements && elements.length > 0) {
            elements.forEach(box => pageObserve.observe(box));
            restorePdfPosition();
        }
        let viewport: PageViewport = page.getViewport({ scale: cachedScale });
        setViewport(viewport);
    };

    const restorePdfPosition = () => {
        const key = readConfig("pdfScrollKey") + projId;
        const scrollPosition = localStorage.getItem(key);
        if (scrollPosition) {
            setTimeout(() => {
                const pdfContainerDiv = document.getElementById('pdfContainer');
                if (pdfContainerDiv) {
                    let scroll = parseInt(scrollPosition);
                    pdfContainerDiv.scrollTop = scroll;
                }
            }, 0);
        }
    }

    const onDocumentLoadSuccess = (pdf: DocumentCallback) => {
        const { numPages } = pdf;
        setNumPages(numPages);
    }

    const getDynStyles = (viewModel: string) => {
        switch (viewModel) {
            case "default":
                return styles.previewBody;
            case "fullscreen":
                return styles.previewFsBody
            default:
                return styles.previewBody;
        }
    }

    const renderPages = (totalPageNum: number | undefined) => {
        if (!totalPageNum || totalPageNum < 1) return;
        const tagList: JSX.Element[] = [];
        for (let i = 1; i <= totalPageNum; i++) {
            tagList.push(
                <Page key={i}
                    className={styles.pdfPage}
                    scale={projAttribute.pdfScale}
                    onLoad={handlePageChange}
                    canvasRef={(element) => updateRefArray(i, element)}
                    onChange={handlePageChange}
                    onRenderSuccess={handlePageRenderSuccess}
                    pageNumber={i} >
                    {curPdfPosition && viewport ? <Highlight position={curPdfPosition}
                        pageNumber={i}
                        viewport={viewport}></Highlight> : <div></div>}
                </Page>
            );
        }
        return tagList;
    }

    const handlePdfScroll = (e: React.UIEvent<HTMLDivElement>) => {
        const scrollTop = e.currentTarget.scrollTop;
        const key = readConfig("pdfScrollKey") + projId;
        localStorage.setItem(key, scrollTop.toString());
    }

    return (
        <div id="pdfContainer" className={getDynStyles(viewModel)} onScroll={(e) => handlePdfScroll(e)}>
            <Document options={options}
                file={curPdfUrl}
                onLoadSuccess={onDocumentLoadSuccess}>
                {renderPages(numPages)}
            </Document>
        </div>
    );
}, (prevProps, nextProps) => {
    let shouldRerender = (prevProps.curPdfUrl === nextProps.curPdfUrl);
    if(shouldRerender) {
        console.warn("pdf will reload: cur:" + prevProps.curPdfUrl + ",next:" + nextProps.curPdfUrl);
    }
    return shouldRerender;
});

export default MemoizedPDFPreview;

I have tried to read the https://github.com/wojtekmaj/react-pdf/wiki/Frequently-Asked-Questions but still did not know how to fixed this issue, how to bind the pdfUrl to this? Am I missing something? I have try to print the log, seems only render onece but the download action triggered multiple time. Here is the whole project: https://github.com/RedDwarfTech/texhub-web

Steps to reproduce

  1. go to 'https://tex.poemhub.top'
  2. create account
  3. create a latex project and compile
  4. the compiled pdf will load multiple times

Expected behavior

the pdf only load one time with one url

Actual behavior

the pdf only load multiple time with one url

Additional information

the whole source code using react-pdf https://github.com/RedDwarfTech/texhub-web

Environment

wojtekmaj commented 4 months ago

It's not downloading multiple times. It's downloading parts of it as you go through the document, avoiding unnecessarily downloading the whole thing and thus speeding up render time.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206

PS. Your code is fine.