Layton Whiteley
Posted on October 2, 2022
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,
};
};
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,
};
};
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,
};
};
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
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
💖 💪 🙅 🚩
Layton Whiteley
Posted on October 2, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.