You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
};
补充排查点

如果替换代码后仍无法显示,按顺序校验:

  1. 查看浏览器控制台是否有Cannot read properties of undefined类的读属性错误,只要存在渲染错误React就会中断组件渲染
  2. 确认customer_id在组件首次渲染时已赋值,skip逻辑不会导致请求一直处于未触发状态
  3. 确认接口返回的listCreativeForm字段为数组类型,而非对象、null等其他类型
  4. 检查外层是否存在ErrorBoundary组件吞掉了渲染错误,导致页面空白但控制台仅输出警告

内容的提问来源于stack exchange,提问作者Andrew Kloos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 02:27:31