DEV Community

Cover image for Using React and styled-components to generate PDFs
Anvil Engineering
Anvil Engineering

Posted on • Originally published at useanvil.com

Using React and styled-components to generate PDFs

Anvil's PDF generation API endpoint supports generating PDFs from HTML and CSS. For the sake of simplicity and interoperability, the endpoint only accepts vanilla HTML & CSS. Obviously writing vanilla HTML & CSS is painful and far out of fashion.

Modern technologies like React, Vue, LESS, SASS, styled-components, etc. allow you to write more modular, reusable code. Fortunately these technologies all compile down to vanilla HTML and CSS.

So, while the endpoint accepts only vanilla HTML and CSS, you can use any technologies you'd like to generate the HTML and CSS then send it to the API. For example, you can use the aforementioned libraries like React and Vue, or even your crazy homegrown template language. As long as a library can generate HTML and CSS strings, you can use it with the API!

In this post, I'll show you how to use React and styled-components in Node to create several PDFs, then ultimately an invoice PDF.

The gist of the post is: use the React and styled-components libraries to generate vanilla HTML + CSS strings, then send those strings to the API. Let's get started.

Set up the scaffolding

The first thing we'll do is set up a quick node script to generate a PDF from plain HTML and CSS. We'll use vanilla HTML and CSS strings at first, then we'll incrementally layer in React and styled-components.

First thing, make sure you have an API key, then install the Anvil Node API client:

yarn add '@anvilco/anvil'
# or
npm install '@anvilco/anvil'
Enter fullscreen mode Exit fullscreen mode

Then this script will generate the PDF:

// generate-pdf.js script

import fs from 'fs'
import path from 'path'
import Anvil from '@anvilco/anvil'

const apiKey = 'YOUR_ANVIL_API_KEY'

// Subsequent code samples will focus on modifying
// this buildHTMLToPDFPayload() function
function buildHTMLToPDFPayload () {
  // We'll change these lines soon!
  const html = '<div>Hello World</div>'
  const css = ''
  return {
    data: {
      html,
      css,
    },
  }
}

async function main () {
  const client = new Anvil({ apiKey })
  const exampleData = buildHTMLToPDFPayload()
  const { statusCode, data, errors } = await client.generatePDF(exampleData)

  if (statusCode === 200) {
    fs.writeFileSync('output.pdf', data, { encoding: null })
  } else {
    console.log(statusCode, JSON.stringify(errors || data, null, 2))
  }
}

main()
Enter fullscreen mode Exit fullscreen mode

Run this script, and you'll see the following output. Great job!

Hello world HTML to PDF
Hello world HTML to PDF

Add babel

Using React jsx syntax in node requires the use of babel. I assume most readers will have babel setup already. If you do, skip this section!

What follows is a super minimal install to get you up and running. Your production environment will likely be much more robust. The first step is installing two core packages, then a couple of presets—the most important being @babel/preset-react.

yarn add -D @babel/core @babel/node @babel/preset-env @babel/preset-react
# or
npm install --save-dev @babel/core @babel/node @babel/preset-env @babel/preset-react
Enter fullscreen mode Exit fullscreen mode

The last step is adding a .babelrc file to the root of your project that uses the installed presets:

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
  ]
}
Enter fullscreen mode Exit fullscreen mode

Now you can run the script with yarn babel-node generate-pdf.js.

Add React

Time to generate some HTML with React. We'll be using server-side rendering via react-dom/server.

Install react and react-dom

yarn add react react-dom
# or
npm install react react-dom
Enter fullscreen mode Exit fullscreen mode

Now we use ReactDOMServer.renderToStaticMarkup to generate the HTML. We're using renderToStaticMarkup instead of renderToString because renderToStaticMarkup omits some attributes React uses for dynamic updates. Since we're generating a PDF, there are no dynamic updates, so we have no use for the extra attributes.

// Import React packages
import React from 'react'
import ReactDOMServer from 'react-dom/server'

// The component to render
const Hello = () => (
  <div>Hello React!</div>
)

function buildHTMLToPDFPayload () {
  // Then generate an HTML string!
  const html = ReactDOMServer.renderToStaticMarkup(
    <Hello />
  )
  // css is still the same for now...
  const css = ''
  return {
    data: {
      html,
      css,
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

Run the script and you're cooking with gas (React):

Hello react HTML to PDF
Hello react HTML to PDF

Add styled-components

Next up is the CSS. We will generate the CSS string with styled-components. First install styled-components:

yarn add styled-components
# or
npm install styled-components
Enter fullscreen mode Exit fullscreen mode

Like React, styled-components has server rendering abilities. Our path forward will be to create a new ServerStyleSheet, render sheet.collectStyles(<YourComponent />), then get all the styles as a string.

A quick snippet shows how it interacts with React.

import { ServerStyleSheet } from 'styled-components'

// Generate the HTML, taking care to render the
// component with styled-components
const sheet = new ServerStyleSheet()
const html = ReactDOMServer.renderToStaticMarkup(
  sheet.collectStyles(
    <Hello />
  )
)
const css = sheet.instance.toString()
Enter fullscreen mode Exit fullscreen mode

Then, here's that snippet in context with some actual styling:

// Import styled-components
import styled, { ServerStyleSheet } from 'styled-components'

// Use styled components
const Container = styled.div`
  font-size: 20px;
`

const Magenta = styled.span`
  color: magenta;
`

const Blue = styled.span`
  color: blue;
`

const Hello = () => (
  <Container>
    Ooh, <Magenta>so</Magenta> <Blue>pretty</Blue>!
  </Container>
)

function buildHTMLToPDFPayload () {
  // Generate the HTML, taking care to render the
  // component with styled-components
  const sheet = new ServerStyleSheet()
  const html = ReactDOMServer.renderToStaticMarkup(
    sheet.collectStyles(
      <Hello />
    )
  )

  // Finally, get the CSS as a string
  const css = sheet.instance.toString()

  return {
    data: {
      html,
      css,
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

The result:

React HTML and styled components to PDF
React & styled components to HTML & CSS to PDF

Insert global styles

You will likely need to inject a few global styles into the PDF. For example, you may want to set the font size, color, page details, etc. You can leverage styled-components’ createGlobalStyle() function. In the last example, we set the Container's font-size to 20px, but we could just as well do that styling in the body rule.

Here's a simplified snippet:

import styled, { ServerStyleSheet, createGlobalStyle } from 'styled-components'

const GlobalStyle = createGlobalStyle`
  body {
    font-size: 20px;
  }
`
const Hello = () => ( /* render hello */ )

const sheet = new ServerStyleSheet()
const html = ReactDOMServer.renderToStaticMarkup(
  sheet.collectStyles(
    <>
      <GlobalStyle />
      <Hello />
    </>
  )
)
const css = sheet.instance.toString()
Enter fullscreen mode Exit fullscreen mode

Using this in our script gives us the same result as the last example:

React HTML and styled components to PDF
Same result with global styles

The whole script

Here we are with the whole enchilada: React, styled-components, and global styles. This script contains everything you need to render whatever you want to PDF by way of React and styled-components.

import fs from 'fs'
import path from 'path'
import Anvil from '@anvilco/anvil'

import React from 'react'
import ReactDOMServer from 'react-dom/server'
import styled, { ServerStyleSheet, createGlobalStyle } from 'styled-components'

const apiKey = 'YOUR_ANVIL_API_KEY'

const GlobalStyle = createGlobalStyle`
  body {
    font-size: 20px;
  }
`

const Package = styled.span`
  color: magenta;
`

const Hello = () => (
  <div>
    Hello from <Package>React</Package> & <Package>styled-components</Package>!
  </div>
)

function buildHTMLToPDFPayload () {
  const sheet = new ServerStyleSheet()
  const html = ReactDOMServer.renderToStaticMarkup(
    sheet.collectStyles(
      <Hello />
    )
  )
  const css = sheet.instance.toString()
  return {
    data: {
      html,
      css,
    },
  }
}

async function main () {
  const client = new Anvil({ apiKey })
  const exampleData = buildHTMLToPDFPayload()
  const { statusCode, data, errors } = await client.generatePDF(exampleData)

  if (statusCode === 200) {
    fs.writeFileSync('output.pdf', data, { encoding: null })
  } else {
    console.log(statusCode, JSON.stringify(errors || data, null, 2))
  }
}

main()
Enter fullscreen mode Exit fullscreen mode

An invoice example

Hello-world examples are cute and all, but you may want to see a real-life example.

We've created React to PDF invoice example using the approach in this blog post. Along with the tech discussed in this post, the invoice example uses advanced HTML & CSS features like page numbering, header / footer rendering, special table features, etc.

React and styled-components invoice pdf example
Invoice PDF generation example using React & styled-components

Summary

You should have all the tools necessary to create PDFs from your own React & styled-components code. You can use other technologies of your choice (e.g. Vue + SCSS) by extrapolating the approach in this post. That is, if it can output HTML and CSS strings, you can use it with the HTML to PDF endpoint.

If you have questions or you're developing something cool with PDFs, let us know at developers@useanvil.com. We’d love to hear from you.

Top comments (0)