Generating a PDF file from React Components

Ozan picture Ozan · Jul 8, 2017 · Viewed 89.2k times · Source

I have been building a polling application. People are able to create their polls and get data regarding the question(s) they ask. I would like to add the functionality to let the users download the results in the form of a PDF.

For example I have two components which are responsible for grabbing the question and data.

<QuestionBox />
<ViewCharts />

I'm attempting to output both components into a PDF file. The user can then download this PFD file. I have found a few packages that permit the rendering of a PDF inside a component. However I failed to find one that can generate PDF from an input stream consisting of a virtual DOM. If I want to achieve this from scratch what approach should I follow ?

Answer

Shivek Khurana picture Shivek Khurana · Jul 10, 2017

Rendering react as pdf is generally a pain, but there is a way around it using canvas.

The idea is to convert : HTML -> Canvas -> PNG (or JPEG) -> PDF

To achieve the above, you'll need :

  1. html2canvas &
  2. jsPDF

import React, {Component, PropTypes} from 'react';

// download html2canvas and jsPDF and save the files in app/ext, or somewhere else
// the built versions are directly consumable
// import {html2canvas, jsPDF} from 'app/ext';


export default class Export extends Component {
  constructor(props) {
    super(props);
  }

  printDocument() {
    const input = document.getElementById('divToPrint');
    html2canvas(input)
      .then((canvas) => {
        const imgData = canvas.toDataURL('image/png');
        const pdf = new jsPDF();
        pdf.addImage(imgData, 'JPEG', 0, 0);
        // pdf.output('dataurlnewwindow');
        pdf.save("download.pdf");
      })
    ;
  }

  render() {
    return (<div>
      <div className="mb5">
        <button onClick={this.printDocument}>Print</button>
      </div>
      <div id="divToPrint" className="mt4" {...css({
        backgroundColor: '#f5f5f5',
        width: '210mm',
        minHeight: '297mm',
        marginLeft: 'auto',
        marginRight: 'auto'
      })}>
        <div>Note: Here the dimensions of div are same as A4</div> 
        <div>You Can add any component here</div>
      </div>
    </div>);
  }
}

The snippet will not work here because the required files are not imported.

An alternate approach is being used in this answer, where the middle steps are dropped and you can simply convert from HTML to PDF. There is an option to do this in the jsPDF documentation as well, but from personal observation, I feel that better accuracy is achieved when dom is converted into png first.

Update 0: September 14, 2018

The text on the pdfs created by this approach will not be selectable. If that's a requirement, you might find this article helpful.