DEV Community

Layton Whiteley
Layton Whiteley

Posted on • Updated on

React and Puppeteer: Pdf generation (client download and print)

Summary

Now that the server is setup. We will now tie everything together with a client that can use this api

Setup UI

Step 1

Let's create some hooks to manage printing and downloding the generated pdf.

The following hook is responsible for:

  • Generating the pdf server-side
  • Generating a blob url for further use

How can the blob url be used after?

  • Printing the PDF
  • Downloading the PDF
  • View the PDF in a client (likely via an iframe)
// file: apps/ui-app/src/app/hooks/use-generate-pdf-blob-url.ts

import { useEffect, useState } from 'react';
import { useDebouncedCallback } from 'use-debounce';
import axios, { AxiosResponse } from 'axios';
import { PDFDocumentData } from '@pdf-generation/constants';
import { PdfDocProps } from '@pdf-generation/pdf-doc';
import { pdfData } from '../app.constants';

const generatePdfDocument = (data: PDFDocumentData<PdfDocProps>) => {
  return axios.post<
    PDFDocumentData<PdfDocProps>,
    AxiosResponse<{ data: number[] }>
  >('/api/pdf/generate', data);
};

export const useGeneratePDFBlobURL = ({ enabled = true } = {}) => {
  const [isGeneratingPdf, setIsGeneratingPdf] = useState(false);
  const [pdfBlobURL, setPdfBlobURL] = useState<string>();
  const [error, setError] = useState();

  const generatePdf = useDebouncedCallback(
    (data) => {
      generatePdfDocument(data)
        .then(({ data: pdfBufferData }) => {
          const blob = new Blob([new Uint8Array(pdfBufferData.data)], {
            type: 'application/pdf',
          });
          const blobURL = URL.createObjectURL(blob);

          setIsGeneratingPdf(false);
          setPdfBlobURL(blobURL);
        })
        .catch((generationError) => {
          setIsGeneratingPdf(false);
          setError(generationError);
        });
    },
    500,
    { maxWait: 2000 }
  );

  useEffect(() => {
    if (!pdfBlobURL && enabled) {
      generatePdf.cancel();
      setIsGeneratingPdf(true);
      generatePdf(pdfData);
    }

    return () => {
      if (pdfBlobURL) {
        URL.revokeObjectURL(pdfBlobURL);
      }
    };
  }, [generatePdf, pdfBlobURL, enabled]);

  return {
    isGeneratingPdf,
    pdfBlobURL,
    error,
  };
};

Enter fullscreen mode Exit fullscreen mode

This next hook can be used to accept the blob url and used as an action to download the PDF

// file: apps/ui-app/src/app/hooks/use-pdf-link-downloader.ts

import { useCallback } from 'react';

export const usePDFLinkDownloader = () => {
  const downloadPdf = useCallback((blobURL: string, fileName: string) => {
    const link = document.createElement('a');
    document.body.appendChild(link);
    link.setAttribute('style', 'display: none');
    link.href = blobURL;
    link.download = fileName;

    link.click();

    setTimeout(() => {
      document.body.removeChild(link);
    }, 300);
  }, []);

  return {
    downloadPdf,
  };
};
Enter fullscreen mode Exit fullscreen mode

This next hook can be used to accept the blob url and used as an action to print the PDF

// file: apps/ui-app/src/app/hooks/use-print-pdf.ts

import { useCallback } from 'react';

export const usePrintPDF = () => {
  const printPdf = useCallback((blobURL: string) => {
    const iframe = document.createElement('iframe');
    document.body.appendChild(iframe);
    iframe.style.display = 'none';
    iframe.src = blobURL;
    iframe.onload = function print() {
      setTimeout(() => {
        iframe.focus();
        iframe?.contentWindow?.print();
      }, 1);
    };
  }, []);

  return {
    printPdf,
  };
};
Enter fullscreen mode Exit fullscreen mode

Thats it!

Now you can formulate the UI however you want to

To view the full solution view the repository

git clone https://github.com/lwhiteley/pdf-generation-experiment
cd pdf-generation-experiment
pnpm i
pnpm nx run-many --target=serve --projects=pdf-server,ui-app
Enter fullscreen mode Exit fullscreen mode

After running these commands, you can then use the app to do the actions listed above.


What's next?

  • Better page break management
  • PDF / Image compression
  • Exploring issues faced with fonts
  • Dockerization
  • starting the server with only one instance of puppeteer

Top comments (0)