React中Material UI折叠表格无法展示接口返回数据如何排查
问题排查结论
你的Apollo请求逻辑本身无异常,控制台能拿到正确返回数据但表格无法渲染,问题全部出在表格渲染层代码,共5个核心错误:
- 第一,API混用:你写的
columns配置是react-table的专属语法,原生Material UI Table组件根本不识别columns、data、Cell、accessor、original这类属性,把这些属性塞给<Table>、<TableRow>完全无效。MUI原生Table需要手动遍历数据拼接<TableCell>渲染,不会自动读取columns配置生成内容。 - 第二,数据引用错误:你在
Row组件里已经通过props.row传入了单行数据,结果渲染单元格时写的是data.values.xxx,这里的data是整个接口返回的数组,根本不存在values属性,读值直接触发JS报错,React捕获到渲染错误后就会中断表格内容渲染。 - 第三,结构缺失:表格完全没写
<TableHead>表头部分,就算行数据读取正确,表格结构也不完整。 - 第四,代码位置错误:你把
propTypes定义、useRowStyles定义、Row子组件写在了Tickets组件函数体中间,每次组件重渲染都会重新创建这些定义,触发不必要的重渲染甚至闭包取值异常。 - 第五,细节错误:editable权限判断条件重复写了两次等于"master"的逻辑,属于无效代码;折叠区域的
colSpan值和实际列数不匹配,展开后会出现布局错位。
修复后可直接运行的代码
替换原有Tickets组件代码即可:
import React, { useContext, useState } from "react"; import PropTypes from 'prop-types'; import { useQuery, useMutation } from "@apollo/react-hooks"; import { makeStyles } from '@material-ui/core/styles'; import Box from '@material-ui/core/Box'; import Collapse from '@material-ui/core/Collapse'; import IconButton from '@material-ui/core/IconButton'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import TableCell from '@material-ui/core/TableCell'; import TableContainer from '@material-ui/core/TableContainer'; import TableHead from '@material-ui/core/TableHead'; import TableRow from '@material-ui/core/TableRow'; import Typography from '@material-ui/core/Typography'; import Paper from '@material-ui/core/Paper'; import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; import CreateTicket from "views/Dashboard/CreateTicket"; import { LIST_CREATIVE_FORM, SUBMIT_CREATIVE_FORM, COMPLETE_CREATIVE_FORM } from "queries/formSubmission"; import { READ_ME } from "queries/users"; import { Context } from "redux/store"; const useRowStyles = makeStyles({ root: { '& > *': { borderBottom: 'unset', }, }, }); function Row(props) { const { row, editable, completeCreativeForm, setSelectedCreative } = props; const [open, setOpen] = useState(false); const classes = useRowStyles(); return ( <React.Fragment> <TableRow className={classes.root}> <TableCell> <IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}> {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} </IconButton> </TableCell> <TableCell align="right">{row.creative_promotion_name}</TableCell> <TableCell align="right">{row.creative_message}</TableCell> <TableCell align="right">{row.creative_start}</TableCell> <TableCell align="right">{row.creative_end}</TableCell> <TableCell align="right">{row.landing_page}</TableCell> <TableCell align="right">{row.notes}</TableCell> <TableCell align="right">{row.bannerads ? "Yes" : "No"}</TableCell> <TableCell align="right">{row.socialads ? "Yes" : "No"}</TableCell> <TableCell align="right">{row.onlinevideo ? "Yes" : "No"}</TableCell> <TableCell align="right">{row.out_of_home ? "Yes" : "No"}</TableCell> <TableCell align="right"> {row.link_to_assets ? <a href={row.link_to_assets} target="_blank" rel="noreferrer">View</a> : ""} </TableCell> <TableCell align="right"> {row.user_submitted ? `${row.user_submitted.first_name} ${row.user_submitted.last_name}` : ""} </TableCell> <TableCell align="right"> <div style={{ color: row.completed ? "green" : "red" }}>{row.completed ? "Yes" : "No"}</div> </TableCell> <TableCell align="right"> <div>{row.user_completed ? `${row.user_completed.first_name} ${row.user_completed.last_name}` : ""}</div> {editable && !row.completed && ( <a href="#" onClick={(e) => { e.preventDefault(); completeCreativeForm({ variables: { id: row.id } }); }} > Complete </a> )} </TableCell> <TableCell align="right"> {editable && !row.completed && ( <a href="#" onClick={(e) => { e.preventDefault(); setSelectedCreative(row); }} > Update </a> )} </TableCell> </TableRow> <TableRow> <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={16}> <Collapse in={open} timeout="auto" unmountOnExit> <Box margin={1}> <Typography variant="h6" gutterBottom component="div"> History </Typography> <Table size="small" aria-label="purchases"> <TableHead> <TableRow> <TableCell>Date</TableCell> <TableCell>Customer</TableCell> <TableCell align="right">Amount</TableCell> <TableCell align="right">Total price ($)</TableCell> </TableRow> </TableHead> <TableBody> {/* 后续直接遍历row.history渲染即可,逻辑和外层行一致 */} </TableBody> </Table> </Box> </Collapse> </TableCell> </TableRow> </React.Fragment> ); } Row.propTypes = { row: PropTypes.object.isRequired, editable: PropTypes.bool.isRequired, completeCreativeForm: PropTypes.func.isRequired, setSelectedCreative: PropTypes.func.isRequired }; export default function Tickets() { const [state, dispatch] = useContext(Context); const [selectedCreative, setSelectedCreative] = useState(null); const customer_id = state.customers?.selected?.id; const { data: me } = useQuery(READ_ME); const { loading, data, refetch } = useQuery(LIST_CREATIVE_FORM, { skip: !customer_id, variables: { customerId: customer_id } }); const [completeCreativeForm, { loading: completing }] = useMutation(COMPLETE_CREATIVE_FORM, { skip: !customer_id, onCompleted: () => { refetch(); } }); const tableData = data?.listCreativeForm || []; const editable = me?.readMe?.user_level === "master"; if (loading) return <div>Loading...</div>; return ( <TableContainer component={Paper}> <Table aria-label="collapsible table"> <TableHead> <TableRow> <TableCell /> <TableCell align="right">Creative or Promotion Name</TableCell> <TableCell align="right">Creative Messaging</TableCell> <TableCell align="right">Start Date</TableCell> <TableCell align="right">End Date</TableCell> <TableCell align="right">Landing Page</TableCell> <TableCell align="right">Notes</TableCell> <TableCell align="right">BanerAds</TableCell> <TableCell align="right">SocialAds</TableCell> <TableCell align="right">OnlineVideo</TableCell> <TableCell align="right">Out of Home</TableCell> <TableCell align="right">Link to Asset</TableCell> <TableCell align="right">Submitted By</TableCell> <TableCell align="right">Completed</TableCell> <TableCell align="right">Completed By</TableCell> <TableCell align="right">Operation</TableCell> </TableRow> </TableHead> <TableBody> {tableData.map((row) => ( <Row key={row.id} row={row} editable={editable} completeCreativeForm={completeCreativeForm} setSelectedCreative={setSelectedCreative} /> ))} </TableBody> </Table> {selectedCreative && <CreateTicket initialData={selectedCreative} onClose={() => setSelectedCreative(null)} />} </TableContainer> ); } Tickets.propTypes = { offline: PropTypes.bool };
补充排查点
如果替换代码后仍无法显示,按顺序校验:
- 查看浏览器控制台是否有
Cannot read properties of undefined类的读属性错误,只要存在渲染错误React就会中断组件渲染 - 确认
customer_id在组件首次渲染时已赋值,skip逻辑不会导致请求一直处于未触发状态 - 确认接口返回的
listCreativeForm字段为数组类型,而非对象、null等其他类型 - 检查外层是否存在ErrorBoundary组件吞掉了渲染错误,导致页面空白但控制台仅输出警告
内容的提问来源于stack exchange,提问作者Andrew Kloos
相关产品推荐
相关产品推荐

