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

React MaterialTable详情面板手风琴API数据渲染延迟问题求助

问题解决:MaterialTable详情面板手风琴数据更新延迟/显示错误

问题现象

在MaterialTable的detailPanel中,点击第二、第三个手风琴时数据无法正确加载;切换到其他行后,手风琴显示的是上一行的数据,更新总是慢一步。

根本原因

当前代码使用组件级全局状态(acc_rata、acc_drata)存储所有行的手风琴数据,所有行的详情面板共享同一状态。当切换行或点击手风琴时,全局状态的更新会被多个行的渲染逻辑干扰,导致数据不同步:

  1. 点击手风琴时才触发ID更新,再通过useEffect调用API,状态更新存在延迟
  2. 多行共用同一状态,新行的渲染会拿到旧状态的数据

解决方案

将每个手风琴的状态管理下沉到独立子组件,让每行的详情面板拥有自己的独立状态,避免状态共享冲突。

修改步骤

  1. 抽离两个需要API请求的手风琴为独立组件
  2. 每个子组件内部管理自己的ID状态和数据状态
  3. 在detailPanel中直接使用这些子组件,传入当前行的rowData.idDoc

修改后的完整代码

import MaterialTable from "material-table";
import React, { useEffect, useState } from "react";
import tableIcons from "../templates/TableIcons";
import getColumns from "./Tbl_DocumentoColumns";
import Edit from "@material-ui/icons/Edit";
import {
  Accordion,
  AccordionSummary,
  AccordionDetails,
} from "@material-ui/core";
import { withRouter } from "react-router";
import { AddBox } from "@material-ui/icons";
import ArrowDownwardOutlinedIcon from "@material-ui/icons/ArrowDownwardOutlined";
import {
  deleteTbl_Documento,
  getTbl_Documento,
} from "../../repo/tbl_documentoRepo";
import { searchRata } from "../../repo/tbl_rataRepo";
import { searchDettaglioRata } from "../../repo/tbl_dettagli_rataRepo";
import { Loading } from "../templates/Loading";

// 抽离Rate手风琴组件
const RateAccordion = ({ docId }) => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const res = await searchRata(docId);
        setData(res.data.data.data);
      } catch (err) {
        console.error("获取Rate数据失败:", err);
        setData([]);
      } finally {
        setLoading(false);
      }
    };
    // 只有当docId有效时才请求
    if (docId) {
      fetchData();
    }
  }, [docId]);

  return (
    <Accordion>
      <AccordionSummary
        expandIcon={<ArrowDownwardOutlinedIcon />}
        aria-controls="rate-panel-content"
        id="rate-panel-header"
      >
        Rate associate
      </AccordionSummary>
      <AccordionDetails>
        <div style={{ columns: 3, width: "100%" }}>
          {loading ? (
            <Loading />
          ) : (
            <ul style={{ fontSize: "14px" }}>
              {data.length > 0 ? (
                data.map((y) => <li key={y.idBol}>{y.idBol}</li>)
              ) : (
                <li>无相关数据</li>
              )}
            </ul>
          )}
        </div>
      </AccordionDetails>
    </Accordion>
  );
};

// 抽离Detail Rate手风琴组件
const DetailRateAccordion = ({ docId }) => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const res = await searchDettaglioRata(docId);
        setData(res.data.data.data);
      } catch (err) {
        console.error("获取Detail Rate数据失败:", err);
        setData([]);
      } finally {
        setLoading(false);
      }
    };
    if (docId) {
      fetchData();
    }
  }, [docId]);

  return (
    <Accordion>
      <AccordionSummary
        expandIcon={<ArrowDownwardOutlinedIcon />}
        aria-controls="detail-rate-panel-content"
        id="detail-rate-panel-header"
      >
        Dettaglio delle rate
      </AccordionSummary>
      <AccordionDetails>
        <div style={{ columns: 3, width: "100%" }}>
          {loading ? (
            <Loading />
          ) : (
            <ul style={{ fontSize: "14px" }}>
              {data.length > 0 ? (
                data.map((adr) => <li key={adr.idBol}>idBol: {adr.idBol}</li>)
              ) : (
                <li>无相关数据</li>
              )}
            </ul>
          )}
        </div>
      </AccordionDetails>
    </Accordion>
  );
};

const Tbl_DocumentoTable = (props) => {
  const history = props.history;
  const [columns, setColumns] = useState(getColumns({}));
  const [loading, setLoading] = useState(false);

  // 删除行逻辑
  const handleRowDelete = (oldData, resolve) => {
    setLoading(true);
    deleteTbl_Documento(oldData.idDoc)
      .then(() => {
        resolve();
        setLoading(false);
      })
      .catch(() => {
        resolve();
        setLoading(false);
      });
  };

  return (
    <div>
      <MaterialTable
        style={{
          marginLeft: 30,
          responsive: true,
          marginTop: 50,
          marginRight: 30,
        }}
        minRows={15}
        title="Lista dei documenti"
        columns={columns}
        data={async (query) => {
          setLoading(true);
          const res = await getTbl_Documento(
            query.page,
            query.pageSize,
            query.search
          );
          setLoading(false);
          return {
            data: res.data,
            page: query.page,
            totalCount: parseInt(res.total),
          };
        }}
        detailPanel={[
          {
            tooltip: "Dettaglio",
            icon: "+",
            openIcon: "-",
            render: (rowData) => {
              return (
                <div>
                  <Accordion>
                    <AccordionSummary
                      expandIcon={<ArrowDownwardOutlinedIcon />}
                      aria-controls="doc-detail-panel-content"
                      id="doc-detail-panel-header"
                    >
                      Dettaglio del documento
                    </AccordionSummary>
                    <AccordionDetails>
                      <div
                        style={{
                          columns: 3,
                          width: "100%",
                          responsive: true,
                        }}
                      >
                        <ul style={{ fontSize: "14px" }}>
                          <li>Catena Servizio: {rowData.idCatenaServizio}</li>
                          <li>Commessa: {rowData.idCommessa}</li>
                          <li>Spedizione Racc: {rowData.spedRacc}</li>
                          <li>Ricezione Sped: {rowData.spedRic}</li>
                          <li>Nome Documento: {rowData.DocNomeFile}</li>
                          <li>
                            Nome Documento Cliente: {rowData.DocNomeFileCliente}
                          </li>
                          <li>
                            ID Cliente sul documento: {rowData.docIDCliente}
                          </li>
                          <li>#Documento: {rowData.docNum}</li>
                          <li>Data documento: {rowData.docData}</li>
                          <li>Destinatario 2: {rowData.docDestinatario2}</li>
                          <li>Destinatario 3: {rowData.docDestinatario3}</li>
                          <li>Destinatario 4: {rowData.docDestinatario4}</li>
                          <li>Destinatario 5: {rowData.docDestinatario5}</li>
                          <li>Destinatario 6: {rowData.docDestinatario6}</li>
                          <li>Codice Avvio: {rowData.codiceAvvio}</li>
                          <li>Dati 1: {rowData.dati1}</li>
                          <li>Dati 2: {rowData.dati2}</li>
                          <li>Dati 3: {rowData.dati3}</li>
                          <li>Dati 4: {rowData.dati4}</li>
                          <li>Dati 5: {rowData.dati5}</li>
                          <li>Spedito: {rowData.spedito}</li>
                          <li>Nome Immagine: {rowData.nomeImmagine}</li>
                          <li>
                            Pagamento abilitato: {rowData.pagamentoAbilitato}
                          </li>
                          <li>UltimaModifica: {rowData.lastUpdate}</li>
                        </ul>
                      </div>
                    </AccordionDetails>
                  </Accordion>
                  {/* 使用抽离的子组件,传入当前行的idDoc */}
                  <RateAccordion docId={rowData.idDoc} />
                  <DetailRateAccordion docId={rowData.idDoc} />
                </div>
              );
            },
          },
        ]}
        options={{
          sorting: true,
          actionsColumnIndex: -1,
          pageSize: 20,
          toolbar: true,
          paging: true,
          responsive: true,
          exportButton: true,
          exportAllData: true,
          exportFileName: "ListaTributi",
        }}
        onRowClick={(event, rowData, togglePanel) => togglePanel()}
        actions={[
          {
            icon: () => <Edit />,
            tooltip: "Edit",
            onClick: (event, rowData) => {
              history.push({
                pathname: `/tbl_documento/update/${rowData.idDoc}`,
                user: rowData,
              });
            },
          },
          {
            icon: () => <AddBox variant="contained" color="secondary" />,
            tooltip: "Add New",
            isFreeAction: true,
            onClick: () => {
              history.push("/tbl_documento/add");
            },
          },
        ]}
        icons={tableIcons}
        editable={{
          onRowDelete: (oldData) =>
            new Promise((resolve) => {
              handleRowDelete(oldData, resolve);
            }),
        }}
      />
    </div>
  );
};
export default withRouter(Tbl_DocumentoTable);

额外优化点

  • 给每个手风琴添加了加载状态提示,提升用户体验
  • 增加了错误处理,避免API请求失败导致的页面异常
  • 移除了原代码中冗余的全局状态(rata、drata等),简化组件逻辑

内容的提问来源于stack exchange,提问作者64Bit1990

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 07:36:23