DEV Community

Layton Whiteley
Layton Whiteley

Posted on • Edited on

4

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

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

Top comments (0)

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay