React MaterialTable详情面板手风琴API数据渲染延迟问题求助
问题解决:MaterialTable详情面板手风琴数据更新延迟/显示错误
问题现象
在MaterialTable的detailPanel中,点击第二、第三个手风琴时数据无法正确加载;切换到其他行后,手风琴显示的是上一行的数据,更新总是慢一步。
根本原因
当前代码使用组件级全局状态(acc_rata、acc_drata)存储所有行的手风琴数据,所有行的详情面板共享同一状态。当切换行或点击手风琴时,全局状态的更新会被多个行的渲染逻辑干扰,导致数据不同步:
- 点击手风琴时才触发ID更新,再通过useEffect调用API,状态更新存在延迟
- 多行共用同一状态,新行的渲染会拿到旧状态的数据
解决方案
将每个手风琴的状态管理下沉到独立子组件,让每行的详情面板拥有自己的独立状态,避免状态共享冲突。
修改步骤
- 抽离两个需要API请求的手风琴为独立组件
- 每个子组件内部管理自己的ID状态和数据状态
- 在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
相关产品推荐
相关产品推荐

