Search code examples
reactjsjspdfhtml2canvas

React Convert Div and Table Component Into PDF With Multiple Pages and CSS


As the title says I need to be able to convert the table into a pdf with a header across multiple pages (just like I have it doing in print).

I've found several sources on how to convert react components to a pdf file. But none cover the combination of requiring a component/table spread across multiple pages and importing the CSS you have too. I have it working for print using the react-to-print Library, but I can't seem to find anything for a pdf. The react-pdf library forms stays you cannot use html elements with it.

full code - https://codesandbox.io/s/react-table-to-pdf-m15em?file=/src/App.js

The pdf function...

  exportPdf = () => {
    const state = this.props.listOfStates.find(state => state.id === this.state.selectedStateId);
    const school = this.props.listOfSchools.find(school => school.id === this.state.selectedSchoolId);
    Promise.resolve(this.setState({ ...this.state, selectedStateNamePdf: state.stateName, selectedSchoolNamePdf: school.schoolName }))
    .then(() => {
      const input = document.getElementById('pdf-element');
      html2canvas(input)
        .then((canvas) => {
          const imgData = canvas.toDataURL('image/png');
          const pdf = new jsPDF();
          pdf.addImage(imgData, 'JPEG', 0, 0);
          pdf.addPage();
          pdf.addImage(imgData, 'JPEG', 0, 0);
          pdf.save("download.pdf");
        })
      ;
    })
  }

The pdf component...

class PdfReportContent extends Component {
  render(){
    const { selectedReportDetails, stateName, schoolName, startDate, endDate } = this.props;
    return (
      <div id="pdf-element" style={{maxWidth: "210mm", width: "100%", height: "100%", position: "relative", margin: "0"}}>
        <div style={{display: "block"}}>
          <div style={{width: "100%", display: "flex", justifyContent: "space-between", padding: "1vh 2.5vh 0"}}>
            <div>
              <h2 className="header-title-one">SCHOOL</h2>
              <h2 className="header-title-two">REPORT</h2>
            </div>
            <div style={{display: "flex"}}>
              <div style={{display: "inline-block", textAlign: "right"}}>
                <h5>{stateName}</h5>
                <h5>{schoolName}</h5>
                <p>{startDate} - {endDate}</p>
              </div>
            </div>
          </div>
          <hr />
          <h3 style={{margin: "10px 0 0 2.5vh"}}>REPORT RESULTS</h3>
        </div>
        <div id="print-list-body">
          {selectedReportDetails.map((classDetails, indexOne) => 
            <div key={indexOne} style={{maxWidth: "200mm", width: "100%", margin: "40px auto 0 auto", boxSizing: "border-box"}}>
              <h2 style={{marginBottom: "10px", color: "#01A3E0"}}>{classDetails.className}</h2>
              {classDetails.classes.map((dateTable, indexTwo, classes) =>
                <table key={indexTwo} className="tbl-reports-list">
                  <tbody>
                    <tr>
                      <th>{dateTable.date}</th>
                      <th>Students</th>
                      <th>Grades</th>
                      <th>Assignment</th>
                      <th>Attendance</th>
                    </tr>
                    {dateTable.instructors.map((instructor, indexThree, instructorRows) =>
                      instructor.students.map((row, indexFour, studentRows) =>
                        indexTwo === classes.length-1 ? 
                        <tr key={indexFour} style={indexFour === studentRows.length-1 ? { borderBottom: "2px solid #01A3E0"} : {}}>
                          {indexFour === 0 ?
                          <td>
                              <h5>{instructor.instructorName}</h5>
                          </td> : <td />
                          }
                          <td style={{backgroundColor: "#ffffff"}}>{row.name}</td>
                          <td style={row.isHonorStudent ? {backgroundColor: "#ffffff"} : {backgroundColor: "#ffedbc"}}>{row.grade}</td>
                          <td style={{backgroundColor: "#ffffff"}}>{row.assignment}</td>
                          <td style={{backgroundColor: "#ffffff"}}>{row.attendance}</td>
                        </tr> : 
                        <tr key={indexFour} style={indexFour === studentRows.length-1 && indexThree !== instructorRows.length-1 ? { borderBottom: "2px solid #01A3E0"} : {}}>
                          {indexFour === 0 ?
                          <td>
                              <h5>{instructor.instructorName}</h5>
                          </td> : <td />
                          }
                          <td style={{backgroundColor: "#ffffff"}}>{row.name}</td>
                          <td style={row.isHonorStudent ? {backgroundColor: "#ffffff"} : {backgroundColor: "#ffedbc"}}>{row.grade}</td>
                          <td style={{backgroundColor: "#ffffff"}}>{row.assignment}</td>
                          <td style={{backgroundColor: "#ffffff"}}>{row.attendance}</td>
                        </tr>
                      )
                    )}
                  </tbody>
                </table>
              )}
            </div>
          )}
        </div>
      </div>
    );
  }
}

Steps to reproduce issue:

  • Select any items in both the dropdowns
  • Click Generate Report
  • Click Download PDF
  • Open pdf and notice how the first and second page show the same thing, and the rest of the table data is missing.

If anyone can fork the project in the codesandbox link and show me what I'm missing it would be much appreciated.


Solution

  • jsPdf has a method to render html, you can use it instead of creating the document manually with images, this is the required change to your code:

       ...
       const input = document.getElementById("pdf-element");
       const pdf = new jsPDF({ unit: "px", format: "letter", userUnit: "px" });
       pdf.html(input, { html2canvas: { scale: 0.57 } }).then(() => {
         pdf.save("test.pdf");
       });
       ...
    

    Noticed that I tweak some configs in jsPdf object creation and when calling .hmtl(), for some reason the pdf gets zoomed in, that's why I addded the scale option for html2canvas (this might as well be platform/browser dependant)

    This is the fork with the changes https://codesandbox.io/s/react-table-to-pdf-forked-2iri9