DEV Community

Cover image for How to Generate Documents with React using Onedoc - A Step-by-Step Guide
Titouan Launay for Onedoc

Posted on

How to Generate Documents with React using Onedoc - A Step-by-Step Guide

Why Use React to Generate Documents?

The React Ecosystem

React is the leading javascript framework for building user interfaces. It is widely used, has a large community and package ecosystem. While React focuses on interactive interfaces, it has recently expanded to server-side rendering and static site generation. While this is not its primary use case, the JSX templating syntax and the component-based architecture make it very well suited for static components, such as websites or documents.

The Onedoc app dashboard is built using React, with charts and complex user interactions.

Advantages of Using React for Document Generation

Using React to generate documents has several advantages:

  • Reusability: React components can be reused across different documents, making it easy to maintain a consistent look and feel.
  • Customization: React components can be easily customized with props, making it easy to create different versions of the same document.
  • Data-driven: React components can be data-driven, making it easy to generate documents from structured data.

How Does it Work?

The underlying principle of using React to generate documents is that React components can be rendered to HTML. This HTML can then be converted to a PDF or other document format using various methods, with respective trade-offs in terms of performance, flexibility and complexity.

Writing Your First Document with React

In this simple step by step guide, we will create a simple receipt using the library react-print-pdf. This library is created and maintained by Onedoc, and is designed to make it easy to generate PDFs from React components.

Local Environment Setup

For this tutorial, we will be using a Next.js project. You can use any other React project, but Next.js is a great choice for server-side rendering and static site generation.

Run these commands to get you started. They will create a new Next.js project and install the necessary dependencies:

npx create-next-app@14 pdf-document-generation --ts --tailwind --app --eslint --no-src-dir --import-alias="@/*"
cd pdf-document-generation
npm install
Enter fullscreen mode Exit fullscreen mode

When prompted, select to use TypeScript and Tailwind CSS.

Now that our project is set up, we can start creating our receipt. We will start the development server to see our changes live:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Let's also open this project in your favorite code editor. If you use VSCode, you can just run code . in the terminal.

Create an API Route

Next.js handles routes by creating a route.ts file in the app directory. The folders and subfolders in the app directory are automatically mapped to routes. We will create a new file app/api/receipt/routets to handle the receipt generation.

import { NextApiRequest } from "next";
import { NextResponse } from "next/server";

export const GET = async (req: NextApiRequest) => {
  const receipt = {
    id: 1,
    date: "2021-01-01",
    total: 100,
  };

  return NextResponse.json({ receipt });
};
Enter fullscreen mode Exit fullscreen mode

Create the Receipt Component

With react-print-pdf we can easily compile React components to PDF. Let's create a new file called app/documents/Receipt.tsx:

export const Receipt = () => {
  return <h1>Receipt</h1>;
};
Enter fullscreen mode Exit fullscreen mode

Install the react-print-pdf Package

react-print-pdf allows several drivers, including Onedoc and PrinceXML. For this tutorial, we will use Onedoc as you can generate unlimited documents with a simple watermark.

First, you should head to the Onedoc app and create an account. Once you have an account, you will get your API key on your dashboard page.

Once you have your API key, you can add it to your .env file (or create a new one if it doesn't exist):

ONEDOC_API_KEY=your-api-key
Enter fullscreen mode Exit fullscreen mode

This document is automatically loaded by Next.js and is available in the process.env object.

Now we can install the react-print-pdf and @onedoc/client packages.

npm i -s @onedoc/react-print @onedoc/client
Enter fullscreen mode Exit fullscreen mode

Generate the PDF

Let's get back into our API route in app/api/receipt/route.ts and add the required code to build the PDF:

import { NextApiRequest } from "next";
import { NextResponse } from "next/server";
import { compile } from "@onedoc/react-print";
import { Receipt } from "@/documents/Receipt";
import { Onedoc } from "@onedoc/client";

const onedoc = new Onedoc(process.env.ONEDOC_API_KEY);

export const GET = async (req: NextApiRequest) => {
  const receipt = {
    id: 1,
    date: "2021-01-01",
    total: 100,
  };

  const { file, error } = await onedoc.render({
    html: await compile(Receipt()),
  });

  if (error) {
    return NextResponse.json({ error }, { status: 500 });
  }

  const pdfBuffer = Buffer.from(file);

  // Return the PDF
  return new Response(pdfBuffer, {
    headers: {
      "Content-Type": "application/pdf",
    },
  });
};
Enter fullscreen mode Exit fullscreen mode

If you open your web browser and go to http://localhost:3000/api/receipt, you should see a PDF file! Your first document is now generated with React.

Your first generated PDF! It is basic, but we are going to extend it soon.

Using a Template

While our document generation is now working, the receipt is very basic. We can use a template to make it look more professional. Let's head to the example receipt template provided by react-print-pdf.

We can just copy and paste the template in our app/documents/Receipt.tsx file, making sure to add an export call. We can also tweak a few things to adapt it to our React structure.

import { Footnote, Tailwind } from "@onedoc/react-print";

export const Receipt = ({
  id,
  date,
  total,
}: {
  id: number;
  date: string;
  total: number;
}) => (
  <Tailwind>
    <div className="font-sans">
      <div className="bg-gradient-to-r from-blue-600 to-blue-400 -z-10 absolute -left-[2cm] right-[25vw] -skew-y-12 h-[100vh] bottom-[95vh]" />
      <div className="bg-gradient-to-r from-blue-600 to-blue-800 -z-20 absolute left-[75vw] -right-[2cm] -skew-y-12 h-[100vh] bottom-[90vh]" />
      <div className="bg-slate-100 -rotate-12 -z-10 absolute -left-[200em] -right-[200em] h-[100vh] top-[75vh]" />
      <main className="text-slate-800 pt-24 h-[90vh] flex flex-col">
        <svg
          version="1.1"
          id="Layer_1"
          xmlns="http://www.w3.org/2000/svg"
          x="0px"
          y="0px"
          viewBox="0 0 24 24"
          className="mx-auto w-32 mb-12 fill-blue-800"
        >
          <g>
            <path
              d="M22.45,12.12c0-2.91-0.99-5.33-3.03-7.34C17.42,2.76,14.96,1.74,12,1.74c-2.93,0-5.4,1.02-7.43,3.05
            C2.56,6.8,1.55,9.26,1.55,12.15c0,0.84,0.11,1.63,0.27,2.37l9.71-7.65h5.01v14.58c1.06-0.5,2.03-1.13,2.91-1.99
            C21.46,17.45,22.45,15.01,22.45,12.12z"
            />
            <path d="M4.91,19.78c1.4,1.26,3.03,2.12,4.9,2.48v-6.32L4.91,19.78z" />
          </g>
        </svg>
        <h1 className="text-center text-2xl text-slate-800">
          Receipt from Acme Inc.
        </h1>
        <p className="pt-2 text-slate-400 text-center">Receipt #{id}</p>
        <div className="p-12 flex-grow bg-white rounded-2xl rounded-t-none shadow-xl shadow-black/10">
          <div className="flex justify-between gap-4">
            <div>
              <div className="text-sm text-gray-400 font-bold uppercase pb-1">
                Amount paid
              </div>
              <div className="flex gap-4 items-center">${total}</div>
            </div>
            <div>
              <div className="text-sm text-gray-400 font-bold uppercase pb-1">
                Date
              </div>
              <div className="flex gap-4 items-center">
                {new Date(date).toLocaleString()}
              </div>
            </div>
            <div>
              <div className="text-sm text-gray-400 font-bold uppercase pb-1">
                Payment method
              </div>
              <div className="flex gap-4 items-center font-mono">
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  width="1200"
                  height="800"
                  version="1.1"
                  viewBox="-96 -98.908 832 593.448"
                  className="h-4"
                >
                  <path
                    fill="#ff5f00"
                    strokeWidth="5.494"
                    d="M224.833 42.298h190.416v311.005H224.833z"
                    display="inline"
                  ></path>
                  <path
                    fill="#eb001b"
                    strokeWidth="5.494"
                    d="M244.446 197.828a197.448 197.448 0 0175.54-155.475 197.777 197.777 0 100 311.004 197.448 197.448 0 01-75.54-155.53z"
                  ></path>
                  <path
                    fill="#f79e1b"
                    strokeWidth="5.494"
                    d="M640 197.828a197.777 197.777 0 01-320.015 155.474 197.777 197.777 0 000-311.004A197.777 197.777 0 01640 197.773z"
                    className="e"
                  ></path>
                </svg>
                0911
              </div>
            </div>
          </div>
          <h2 className="text-slate-600 font-bold text-sm py-6 pt-12 uppercase">
            Summary
          </h2>
          <div className="bg-slate-100 px-6 py-2 rounded-md">
            <table className="w-full">
              <tr className="border-b text-slate-500">
                <td className="py-4">Basic Pro Plan</td>
                <td className="py-4">$12.99</td>
              </tr>
              <tr className="font-bold text-slate-700">
                <td className="py-4">Amount charged</td>
                <td className="py-4">$12.99</td>
              </tr>
            </table>
          </div>
          <hr className="my-6" />
          This is some additional content to to inform you that Acme Inc. is a
          fake company and this is a fake receipt. This is just a demo to show
          you how you can create a beautiful receipt with Onedoc.{" "}
          <Footnote>
            Some additional conditions may apply. This template comes from the
            react-print library, available at https://react.onedoclabs.com/
          </Footnote>
        </div>
      </main>
    </div>
  </Tailwind>
);
Enter fullscreen mode Exit fullscreen mode

Let's also update our API route to pass the receipt data to the component:

import { NextApiRequest, NextApiResponse } from "next";
import { NextResponse } from "next/server";
import { compile } from "@onedoc/react-print";
import { Receipt } from "@/documents/Receipt";
import { Onedoc } from "@onedoc/client";

const onedoc = new Onedoc(process.env.ONEDOC_API_KEY);

export const GET = async (req: NextApiRequest) => {
  const receipt = {
    id: 1,
    date: "2021-01-01",
    total: 100,
  };

  const { file, error } = await onedoc.render({
    html: await compile(Receipt(receipt)),
  });

  if (error) {
    return NextResponse.json({ error }, { status: 500 });
  }

  const pdfBuffer = Buffer.from(file);

  // Return the PDF
  return new Response(pdfBuffer, {
    headers: {
      "Content-Type": "application/pdf",
    },
  });
};
Enter fullscreen mode Exit fullscreen mode

Let's refresh our web browser and go to http://localhost:3000/api/receipt. You should now see a beautiful receipt!

The receipt is now much more professional, with a beautiful design and a fake company disclaimer.

Considerations and Best Practices

Data-Driven Documents

In this tutorial, we hardcoded the receipt data. In a real-world application, you would likely fetch the receipt data from an API or a database. This is very easy to do with your backend framework of choice, and you can pass the data to the React component as props.

Performance Considerations

Generating PDFs can be a CPU-intensive task, especially for complex documents. You should consider offloading the PDF generation to a separate service or a background job to avoid blocking the main thread. Onedoc provides a REST API that you can use to generate PDFs in the background.

Improving your Documents

Using React makes it super easy to update and extend your documents. You can have a look at other examples using the react-print-pdf library to see how you can create more complex documents, such as invoices, reports, or even entire books.

This article was originally published on the Onedoc blog

Top comments (0)