Show Loader when PDF is Generating
Is your feature request related to a problem? Please describe.
I display a PDF document with PDFViewer in a conditionally rendered component. A button click controls the rendering, When the component is rendered, a blank white page is displayed until the PDF has generated and loaded. This white flash is pretty jarring, esp. when working in dark mode.
Describe the solution you'd like
I'd like to:
- Have the viewer show a loader before the PDF is loaded.
- Control the color of that blank white page.
Describe alternatives you've considered
I've tried using a state prop, setIsPDFRendering passed down into the Viewer and then down to the Document.
This is set to true when the loadContent function is run and set to false in onRender as shown below:
<Document
author={penName}
title={title}
onRender={() => {
setIsPDFRendering(false);
}}
>
<Page size={pageSize} style={styles.page} wrap={true}>
{loadContent()}
</Page>
</Document>
function loadContent() {
console.log("Set isPDFRendering to true");
setIsPDFRendering(true);
...
My thought was that I could use the isPDFRendering in parent components to show a loading overlay or something to cover the blank white page until the PDF is displayed.
The odd part about this is that the state is not getting updated in the parent in line with my expectations.
To test, I've set the button that renders the PDFViewer component to be turn red when isPDFRendering = true.
Then I visually compare the button color change with theconsole.log("Set isPDFRendering to true") in the loadContent function. The timing is way off. There are seconds that pass after the Set isPDFRendering to true and the button color changes. Sometimes the state never gets updated at all.
It is very likely that I'm doing something wrong, but this behavior seems unique to react-pdf/renderer. As this pattern of using parent state is rather common.
Additional context
This screen capture shows the rendering of an 80 page document. The icon should turn red and it never does, but you can see the blank white page.
https://github.com/diegomura/react-pdf/assets/26792769/ffbaa3ea-8106-438c-8374-f5f5d618a47f
I'm using PDFViewer and have the same problem. I would like to show <Loader/> fallback when PDF is rendering. I read documentation and cannot see anything i could use to show that loader. Thanks in advance
@kimfucious @lukaszszmolke did you ever find a solution to this?
As a hack I'm positioning a loading spinner behind the PDFViewer component. Seems to work OK.
i found solution check iframe loaded like this
const iframeRef = useRef<HTMLIFrameElement>(null);
const [isIFrameLoaded, setIsIFrameLoaded] = useState<boolean>(false);
const iframeCurrent = iframeRef.current;
useEffect(() => {
iframeCurrent?.addEventListener('load', () => setIsIFrameLoaded(true));
return () => {
iframeCurrent?.removeEventListener('load', () => setIsIFrameLoaded(true));
};
}, [iframeCurrent]);
{isIFrameLoaded === false ?
<div className="flex h-[81vh]">
<div className="m-auto">
<CustomLoading /> Rendering PDF
</div>
</div>
: null}
<div className={`flex [&>*]:w-full ${isIFrameLoaded === true ? "h-[81vh]" : "h-0"}`}>
<PDFViewer height={'100%'} width={'100%'} showToolbar={true} className="rounded-md" innerRef={iframeRef}>
<MyReport items={reports} />
</PDFViewer>
</div>
work for me
Thanks for the above, but I'd recommend using the built-in functionality:
const [instance, update] = usePDF({ document: <DocumentAsSeparateComp props={propsIfAny} /> });
{ instance.loading ? 'loading...' : ( <PDFViewer> <DocumentAsSeparateComp props={propsIfAny} /> </PDFViewer> }
It is possible as shown above
@jchibbs100 thanks for the note about usePDF, I haven't tried that yet with PDFViewer component 👍
Can you edit your example above to be complete? Eg. how are you sharing the PDF document between document and DocumentAsSeparateComp?
This is the version that I ended up with, it's a bit more complicated than the versions from @pjatupon or @jchibbs100 :
"use client";
// ...
export function PdfPreview({ invoice, organization }: Props) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const [isIframeLoading, setIsIframeLoading] = useState(true);
useEffect(() => {
const iframe = iframeRef.current;
if (!iframe) return;
if (iframe.contentDocument?.readyState === "complete") {
setIsIframeLoading(false);
return;
}
setIsIframeLoading(true);
function handleLoad() {
setIsIframeLoading(false);
}
iframe.addEventListener("load", handleLoad);
return () => {
iframe.removeEventListener("load", handleLoad);
};
}, [invoice, organization]);
return (
<>
<h3>
Preview {isIframeLoading && <Spinner className="ml-2 inline-block" />}
</h3>
<PDFViewer
innerRef={iframeRef}
showToolbar={false}
style={{ aspectRatio: "1.414 / 2" }}
>
<PdfDocument invoice={invoice} organization={organization} />
</PDFViewer>
</>
);
}
export function PdfDocument({ invoice, organization }: Props) {
return (
<Document>
// ...
</Document>
);
}
- source: https://github.com/karlhorky/invoiceo/blob/02d380e0a746dd02e4e1c5f8fbd3fc28cff898d9/src/components/invoices/preview/pdf-preview.tsx#L1
The downside of these approaches is that the "loading time" when the iframe reports a loading state is very minimal - most of the time is spent displaying the field with the gray background, (rendering / drawing the PDF in the browser if I understand correctly), once the iframe has loaded already.
So the spinner actually doesn't show up for very long - sometimes doesn't show up at all:
https://github.com/user-attachments/assets/ea1d0b83-4be3-4faf-9145-b334111b069c
In a first bit of research, it appears there is no "drawn" or "rendered" event from the browser when a PDF document in an iframe / embed element is finally fully displayed.
Wonder if there's something inside @react-pdf/pdfkit that would help here?