import { memo, useLayoutEffect, useMemo } from 'react'; import { useOperatingSystem } from '~/hooks'; export interface PDFViewerProps { src: string; onLoad?: (event: HTMLElementEventMap['load']) => void; onError?: (event: HTMLElementEventMap['error']) => void; className?: string; crossOrigin?: React.ComponentProps<'link'>['crossOrigin']; } export const PDFViewer = memo( ({ src, onLoad, onError, className, crossOrigin }: PDFViewerProps) => { const os = useOperatingSystem(true); // Ignore empty urls const href = !src || src === '#' ? null : src; // Use link preload as a hack to get access to an onLoad and onError events for the object tag // as well as to normalize the URL const link = useMemo(() => { if (href == null) return null; const link = document.createElement('link'); link.as = 'fetch'; link.rel = 'preload'; if (crossOrigin) link.crossOrigin = crossOrigin; link.href = href; link.addEventListener('load', () => link.remove()); link.addEventListener('error', () => link.remove()); return link; }, [crossOrigin, href]); // The useLayoutEffect is used to ensure that the event listeners are added before the object is loaded // The useLayoutEffect declaration order is important here useLayoutEffect(() => { if (!link) return; if (onLoad) link.addEventListener('load', onLoad); if (onError) link.addEventListener('error', onError); return () => { if (onLoad) link.removeEventListener('load', onLoad); if (onError) link.removeEventListener('error', onError); }; }, [link, onLoad, onError]); useLayoutEffect(() => { if (!link) return; document.head.appendChild(link); return () => link.remove(); }, [link]); // Use link to normalize URL return link ? ( os === 'macOS' ? ( // FIX-ME: Using isn't working in macOS for some reason