Building a pdfmake React Renderer using React Reconciler
Tags: react, typescript, javascript, pdfmake
Published: 2023-11-26 17:54:32

In this blog, I talk about a new personal project I've been working on: [react-pdfmake-reconciler](https://danv.im/react-pdfmake-reconciler/).

![Inspecting react-pdfmake-reconciler components using React Developer Tools](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gmaqhnlf2sl1sx8m2wfi.png)

## Background

I've recently been using [pdfmake](https://www.npmjs.com/package/pdfmake) at work behind a React website. There are some PDF JavaScript libraries online and some even offer React interfacing like [React PDF](https://react-pdf.org/), but the reason the team have probably chosen to use pdfmake in particular was because of its **table** layout. pdfmake's table can repeat its header rows when it overflows to the next page and rows can be kept together. Although not as powerful and flexible as LaTeX for example, it's much easier to integrate with a JavaScript/TypeScript source code with a lower bar of entry.

## Slight issues

### Code organization

pdfmake's input is a declarative JavaScript object, (as opposed to command based like PostScript/Turtle.) Style is provided either inline or named. So, it is easy to think of extracting similar bits of code into functions or "components". But using these functions sometimes feels a bit less ergonomic than desired.

```ts
// Example pdfmake Content function
const getSection = (showContent: boolean): ContentStack => ({
  stack: [
    'Line 1',
    showContent ? 'Line 2' : null,
    'Line 3'
  ].filter(isNotNull)
})

const isNotNull = <T>(x: T | null): x is T =>
  x !== null
```

The above function returns a `ContentStack`, one of the `Content` types of pdfmake. But `Content` can also be its own array, i.e.

```ts
type Content =
  | string
  | number
  | Content[]
  | ContentStack
  | OtherTypesOfContents
```

When using our content functions, we sometimes need to be knowledgeable of whether the return type is an array or not, because pdfmake sometimes treats them differently.

```ts
const contents: Content[] = [
  getSingleContent(),
  ...getManyContents(),
]
```

And you can imagine what happens if a deeply nested function requires a parameter from the input call. That parameter would need to be passed down through every function call, as opposed to something like React Context.

### Debugging process

pdfmake's PDF generation process works on the server side as well as the client side. For complex use cases, it is understandable developers can create a test live preview on the client side while tweaking logic and styles. The final visual of the PDF can be tested manually, but what if we have a more powerful way of checking out our content functions? (:wink:, :wink:, React Developer Tools)

### Developer familiarity

The content of our entire project is basically written in React JSX, except for this pdf generation pipeline. If writing document definitions of pdfmake was as similar to React as possible, or even directly inside React, then it could help new developers get into this side of the code base a lot quicker, with less impedance on writing nicely organized React code.

## Adapting pdfmake to React

pdfmake is quite a popular JavaScript library for PDF generation. However, there didn't seem to be a popular React interface for it when I initially went looking for one. This is also when it piqued my interest to make one myself as an exercise on my weekend.

I immediately thought of these ways of solving the issue:

### Solution 1: Walk the JSX tree

The simplest solution I thought of that didn't involve any special libraries or toolchain configuration: Write React components and serialize into pdfmake input by walking the `ReactNode` tree and transforming each node.

### Solution 2: Custom jsx factory

I've used [Theme UI](https://theme-ui.com/) before and they have an optional jsx factory that allows a special [`sx={}`](https://theme-ui.com/guides/jsx-pragma) prop to style React components. This way, we can still write in JSX, but instead of them turning into `ReactNode`, we can serialize them into the pdfmake input format.

It turns out someone had this idea a few years ago and has already written a package for this for pdfmake: [JSX PDF](https://github.com/schibsted/jsx-pdf)

### Solution 3: Writing a custom React Renderer

I've known [react-reconciler](https://www.npmjs.com/package/react-reconciler) for a while but haven't had time to try it out (probably from usage by [React Three Fiber](https://docs.pmnd.rs/react-three-fiber)). This package is an official React package by Facebook, that allows you to write renderers similar to React Native/React DOM. This also means we can use pretty much all of React's capability if I use React Reconciler.

### Final solution

I've decided in the end to use react-reconciler mostly because I wanted to take this opportunity to try and use it. But there are other reasons too. Since the result will be in actual React, we can use React features like Components, Hooks, Context, etc. without much consideration. React Reconciler can also inject into React Developer Tools, providing great dev time experience.

## Using React Reconciler

There's a bit more setting up to do using React Reconciler, and since the package is still marked as experimental, there's missing docs here and there in the source code.

But this was also a great opportunity for me to learn more and re-familiarize myself with React. Although I have been using React for a few years now, I have not really delved into the mechanism of React and I only vaguely remember React's lifecycle. But I have subscribed to newsletters, so I sometimes read articles like this: https://v8.dev/blog/react-cliff, and how React Fiber uses monomorphism to optimize for modern JavaScript engines because of how they access JavaScript objects.

Over my weekend, I checked out react reconciler's source code and referenced against usages in other projects like [react-art](https://github.com/reactjs/react-art) and [react-three-fiber](https://github.com/pmndrs/react-three-fiber) and finished the implementation using react-reconciler. I polished my code base after work over the next week.

In the end, I have created this package: [react-pdfmake-reconciler](https://github.com/danvim/react-pdfmake-reconciler). (Not a good name, but react-pdfmake was already taken). I have created a simple StackBlitz playground here: (Slight note: there's use of `<iframe>` for your browser to preview the PDF. This doesn't work well on mobile.)

{% embed https://stackblitz.com/edit/react-pdfmake-reconciler?embed=1&file=src%2FApp.tsx %}

If you are interested in this project, check out these links:

- https://danv.im/react-pdfmake-reconciler/
- https://www.npmjs.com/package/react-pdfmake-reconciler
- https://github.com/danvim/react-pdfmake-reconciler
- https://stackblitz.com/edit/react-pdfmake-reconciler?file=src%2FApp.tsx