React页面PDF下载报错:Cannot read properties of undefined (reading 'length')
PDF下载报错:Cannot read properties of undefined (reading 'length')
问题场景
点击页面中的「Download PDF」按钮时,抛出TypeError错误:Cannot read properties of undefined (reading 'length'),无法生成并下载PDF文件。
错误原因
错误出在jsPDF-autotable的html配置项上:当前代码传入的是未挂载到DOM中的div元素,autoTable无法正确解析该元素内的表格结构,导致内部处理时尝试读取某个未定义对象的length属性,触发报错。
解决方案
提供两种可行修复方式:
方案1:直接用表头和数据生成表格(推荐)
跳过手动创建DOM元素的步骤,直接将表头和当前页数据传入autoTable,避免DOM解析问题:
- 定义表头数组
- 将当前页数据转换为表格行格式
- 通过
head和body参数传递给autoTable
方案2:临时挂载DOM元素
如果坚持使用html配置,需先将创建的表格容器临时挂载到页面,生成PDF后再移除:
- 生成PDF前,把
tableContainer添加到document.body - 完成PDF保存后,立即从DOM中移除该元素
修改后的代码(方案1实现)
import * as React from "react"; import { useState, useEffect } from "react"; import Button from "@mui/material/Button"; import axios from "axios"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableCell from "@mui/material/TableCell"; import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Paper from "@mui/material/Paper"; import jsPDF from "jspdf"; import "jspdf-autotable"; const ButtonUsage = () => { const [data, setData] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [showTable, setShowTable] = useState(false); const recordsPerPage = 10; const pagesToShow = 5; useEffect(() => { if (showTable) { getProductData(); } }, [showTable]); const getProductData = () => { axios.get("https://jsonplaceholder.typicode.com/todos").then((res) => { setData(res.data); }); }; const maxPages = data.length > 0 ? Math.ceil(data.length / recordsPerPage) : 1; const paginate = (pageNumber) => { setCurrentPage(pageNumber); }; const downloadCurrentPageAsPDF = () => { if (data.length > 0) { generatePDF(); } else { console.error("数据为空或未定义"); } }; const generatePDF = () => { const doc = new jsPDF(); // 定义表头 const headers = [["userId", "id", "title", "completed"]]; // 处理当前页数据为表格行格式 const currentRecords = data.slice( (currentPage - 1) * recordsPerPage, currentPage * recordsPerPage ); const bodyData = currentRecords.map(row => [ row.userId, row.id, row.title, row.completed ? "Yes" : "No" ]); // 生成PDF表格 doc.autoTable({ head: headers, body: bodyData, styles: { border: 1, padding: 8 }, headStyles: { fillColor: "#f0f0f0" } }); doc.save("current_page.pdf"); }; return ( <React.Fragment> <Button variant="contained" onClick={() => setShowTable(true)}> Get Data </Button> {showTable && data.length > 0 && ( <div> <TableContainer component={Paper}> <Table sx={{ minWidth: 650 }} size="small" aria-label="a dense table" > <TableHead> <TableRow> <TableCell>userId</TableCell> <TableCell>id</TableCell> <TableCell>title</TableCell> <TableCell>completed</TableCell> </TableRow> </TableHead> <TableBody> {data .slice( (currentPage - 1) * recordsPerPage, currentPage * recordsPerPage ) .map((row) => ( <TableRow key={row.id} sx={{ "&:last-child td, &:last-child th": { border: 0 } }} > <TableCell component="th" scope="row"> {row.userId} </TableCell> <TableCell>{row.userId}</TableCell> <TableCell>{row.id}</TableCell> <TableCell>{row.title}</TableCell> <TableCell>{row.completed ? "Yes" : "No"}</TableCell> </TableRow> ))} </TableBody> </Table> </TableContainer> <div className="pagination"> <button onClick={() => paginate(1)} disabled={currentPage === 1}> First </button> <button onClick={() => paginate(currentPage - 1)} disabled={currentPage === 1} > Previous </button> {Array.from({ length: Math.min(pagesToShow, maxPages) }, (_, i) => ( <button key={i + 1} onClick={() => paginate(i + 1)} className={currentPage === i + 1 ? "active" : ""} > {i + 1} </button> ))} <button onClick={() => paginate(currentPage + 1)} disabled={currentPage === maxPages} > Next </button> <button onClick={() => paginate(maxPages)} disabled={currentPage === maxPages} > Last </button> <button onClick={downloadCurrentPageAsPDF}>Download PDF</button> </div> </div> )} </React.Fragment> ); }; export default ButtonUsage;
方案2代码修改(仅修改generatePDF函数)
const generatePDF = () => { const tableContainer = document.createElement("div"); tableContainer.style.width = "100%"; tableContainer.style.overflow = "auto"; const table = document.createElement("table"); table.style.borderCollapse = "collapse"; table.style.width = "100%"; table.style.marginBottom = "20px"; const tableHead = table.createTHead(); const headRow = tableHead.insertRow(0); const headers = ["userId", "id", "title", "completed"]; headers.forEach((headerText, index) => { const cell = headRow.insertCell(index); cell.textContent = headerText; cell.style.border = "1px solid black"; cell.style.padding = "8px"; }); const tableBody = table.createTBody(); const currentRecords = data.slice( (currentPage - 1) * recordsPerPage, currentPage * recordsPerPage ); currentRecords.forEach((row) => { const newRow = tableBody.insertRow(); const rowData = [ row.userId, row.id, row.title, row.completed ? "Yes" : "No", ]; rowData.forEach((cellData, index) => { const cell = newRow.insertCell(index); cell.textContent = cellData; cell.style.border = "1px solid black"; cell.style.padding = "8px"; }); }); tableContainer.appendChild(table); // 临时挂载到DOM document.body.appendChild(tableContainer); const doc = new jsPDF(); doc.autoTable({ html: tableContainer, }); doc.save("current_page.pdf"); // 移除临时元素 document.body.removeChild(tableContainer); };
内容的提问来源于stack exchange,提问作者Sandeep
相关产品推荐
相关产品推荐

