Material React Table虚拟化与行展开功能整合问题求助
Material React Table 无限滚动与行展开功能整合问题解决
问题核心
在结合Material React Table的无限滚动(虚拟化)和行展开折叠功能时,展开父行后子行无法正常渲染,本质是数据管理与虚拟化渲染的协同逻辑出现冲突:
- 手动用
completeData.current缓存数据导致useInfiniteQuery的数据集不完整,getSubRows无法找到子行 - 展开行时未正确触发数据更新,或数据未被纳入虚拟化表格的数据集
- 虚拟化组件未在展开状态变化时重新计算行布局
代码修正方案
1. 重构useInfiniteQuery数据逻辑
移除手动缓存,让useInfiniteQuery统一管理顶级行和子行数据,利用queryKey中的expanded状态自动触发数据更新:
const { data, fetchNextPage, isError, isFetching, isLoading } = useInfiniteQuery({ queryKey: ['table-data', columnFilters, expanded, sorting], queryFn: async ({ pageParam = 0 }) => { const offset = pageParam * fetchSize; const preQueryFilters = props.preQueryRes.decompose()[0].rawData(); let processedResp; // 判断是否处于展开状态 const expandedRowEntry = Object.entries(expanded).find(([_, isExpanded]) => isExpanded); const isExpanding = !!expandedRowEntry; const expandedRowId = isExpanding ? expandedRowEntry[0].split('-').pop() : null; if (isExpanding && pageParam === 0) { // 展开时:重新获取已加载的所有顶级行 + 当前展开行的子行 const loadedTopRowCount = (data?.pages.length || 1) * fetchSize; const topLevelResponse = await loadCubeData( addPreQueryResAsFilter(props.query, preQueryFilters.slice(0, loadedTopRowCount)) ); const topLevelData = processData(topLevelResponse); // 获取子行数据 const [subDim, readyQuery] = addSubQueryFilter(props.query, expandedRowId); const subRowResponse = await loadCubeData(readyQuery); const subRowData = processData(subRowResponse, subDim); processedResp = [...topLevelData, ...subRowData]; } else { // 正常无限滚动:加载下一页顶级行 const response = await loadCubeData( addPreQueryResAsFilter(props.query, preQueryFilters.slice(offset, offset + fetchSize)) ); processedResp = processData(response); } return { data: processedResp, meta: { totalRowCount: preQueryFilters.length || 0 }, }; }, initialPageParam: 0, getNextPageParam: (_lastGroup, groups) => { // 展开状态下禁止加载下一页,避免数据混乱 const isExpanding = Object.values(expanded).some(Boolean); return isExpanding ? undefined : groups.length; }, refetchOnWindowFocus: false, });
2. 简化数据集生成
确保flatReportData包含所有已加载的顶级行和子行:
const flatReportData = useMemo(() => { return data?.pages?.flatMap(page => page.data) || []; }, [data]);
3. 调整表格组件配置
- 仅在非展开状态下触发无限滚动
- 通知虚拟化组件在展开状态变化时重新计算布局:
// 新增:展开状态变化时重新测量虚拟行 useEffect(() => { rowVirtualizerInstanceRef.current?.measure(); }, [expanded]); // 表格组件 <MaterialReactTable columns={tableColumns.current} data={flatReportData.filter(r => !r.parentId)} enablePagination={false} enableRowVirtualization getRowId={(originalRow) => originalRow.id} muiTableContainerProps={{ ref: tableContainerRef, sx: { maxHeight: '600px' }, onScroll: (event) => { // 仅在非展开状态下触发无限滚动加载 const isExpanding = Object.values(expanded).some(Boolean); if (!isExpanding) { fetchMoreOnBottomReached(event.target); } }, }} muiToolbarAlertBannerProps={isError ? { color: 'error', children: 'Error loading data' } : undefined} renderBottomToolbarCustomActions={() => ( <Typography> Fetched {totalFetched} of {totalDBRowCount} total rows. </Typography> )} state={{ expanded, columnFilters, isLoading, showAlertBanner: isError, showProgressBars: isFetching, sorting, }} rowVirtualizerInstanceRef={rowVirtualizerInstanceRef} rowVirtualizerOptions={{ overscan: 4 }} onColumnFiltersChange={setColumnFilters} onExpandedChange={setExpanded} enableTopToolbar={false} enableExpanding getRowCanExpand={(row) => row.original.level < levels.length} getSubRows={(row) => flatReportData.filter(r => r.parentId === row.id)} onSortingChange={setSorting} paginateExpandedRows={false} enableColumnFilters={false} enableColumnActions={false} enableFullScreenToggle={false} enableDensityToggle={false} initialState={{ density: 'spacious' }} enableHiding={false} />
关键最佳实践
- 统一数据管理:始终让useInfiniteQuery管理所有表格数据,避免手动缓存导致的数据不一致
- 状态驱动查询:利用
queryKey中的expanded状态自动触发数据更新,无需手动判断 - 虚拟化适配:展开状态变化时强制虚拟器重新测量,确保子行布局正确
- 互斥操作限制:展开行时禁止无限滚动加载,避免顶级行与子行的数据冲突
内容的提问来源于stack exchange,提问作者Hassan Akhlaq
相关产品推荐
相关产品推荐

