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

开启服务端行分组的AG Grid切换过滤器时触发多次API调用问题

AG Grid服务端行分组切换过滤器触发多次API调用问题

问题描述

启用服务端行分组(server-side row grouping)的AG Grid组件,切换过滤器时会触发多次API调用,极端情况甚至持续调用。未开启行分组的同配置组件无此问题,每次切换仅触发一次调用。

调试确认:

  • 调用refreshServerSide()的useEffect仅触发一次
  • getServerSideDatasource的getRows方法中,params.request会返回两次,导致两次API请求

已尝试操作

  • 确认refreshServerSide()仅执行一次
  • 验证切换过滤器时params.request返回两次
  • 对比未启用行分组的同逻辑组件,确认后者运行正常

预期与实际行为

  • 预期:切换过滤器仅触发一次API调用
  • 实际:切换单个过滤器触发两次或多次调用,部分场景持续调用

组件相关代码

const memoizedFilters = useMemo(() => filters, [JSON.stringify(filters)]);

useEffect(() => {
 if(filtersRef.current !== filters) {
 filtersRef.current = filters;
  if (gridApiRef.current && !gridApiRef.current.isDestroyed()) {
   console.log("Refreshing server-side data with new filters");
  
    gridApiRef.current.refreshServerSide();
  }
  }
}, [memoizedFilters]);

const autoGroupColumnDef = useMemo<ColDef>(() => ({
  headerName: "Group",
  cellRendererParams: {
    suppressCount: false,
    suppressDoubleClickExpand: false,
    innerRenderer: () => "",
  },
}), []);
const getServerSideDatasource = (): IServerSideDatasource => {
 return {
   getRows: async (params: IServerSideGetRowsParams) => {
     console.log("Requesting rows", params.request);
    
     const newStartRow = params.request.startRow ?? 0;
     const newEndRow = params.request.endRow ?? 10;

     if (params.request.groupKeys.length && params.request.groupKeys[0] !== "") {
       const campaign_id = params.request.groupKeys[0];
       const campaignFilters = filtersRef.current;

       try {
         const res = await fetchCampaignOverview({ startRow: newStartRow, endRow: newEndRow, campaign_id, ...campaignFilters });
         if (res.rows.length) {
           params.success({ rowData: res.rows, rowCount: res.totalRows });
         } else {
           params.success({ rowData: [], rowCount: 0 });
         }
       } catch (error) {
         console.error("Error fetching rows:", error);
         params.fail();
       }
     } else if (params.request.groupKeys[0] === "") {
      
       const unGroupedData = fetchedDataRef.current.filter((item) => item.campaign_id === "");
       console.log("Fetching ungrouped data", unGroupedData);
       params.success({ rowData: unGroupedData, rowCount: unGroupedData.length });
     } else {
       try {
         const campaignFilters = filtersRef.current;
         const res = await fetchCampaignOverview({ startRow: newStartRow, endRow: newEndRow, ...campaignFilters });
         if (res.rows.length) {
           const unbalancedData = res.rows.map((item) => ({
             ...item,
             campaign_id: item.has_child ? item.campaign_id : "",
           }));

           fetchedDataRef.current = unbalancedData;
           const groupedData = unbalancedData.filter((item) => item.campaign_id !== "");
           const unGroupedData = unbalancedData.filter((item) => item.campaign_id === "");
           if (unGroupedData.length > 0) groupedData.push(unGroupedData[0]);
           params.success({ rowData: groupedData, rowCount: res.totalRows });
         } else {
           params.success({ rowData: [], rowCount: 0 });
         }
       } catch (error) {
         params.fail();
         console.error("Error fetching rows:", error);
       }
     }
   },
 };
};

const onGridReady =  (params: GridReadyEvent) => {
 console.log("Grid is ready");
  if (!gridApiRef.current || gridApiRef.current.isDestroyed()) {
    gridApiRef.current = params.api
    const datasource = getServerSideDatasource();
    params.api.setGridOption("serverSideDatasource", datasource);
  }
};

 
 const paginationPageSizeSelector = useMemo(() => [10, 20, 50, 100], []);

return (
  <>
    <CampaignFilters
      onFilterChange={(filters: CampaignFilter) => { setFilters(filters); }}
    />
    <TableContainer>
      <AgGridReact
        autoGroupColumnDef={autoGroupColumnDef}
        cacheBlockSize={paginationPageSize}
        columnDefs={colDefs}
        defaultColDef={defaultColDef}
        domLayout="autoHeight"
        blockLoadDebounceMillis={1000}
       //  getServerSideGroupKey={(params: { data: CampaignOverviewRowData }) => params.data.campaign_id || ""}
        groupAllowUnbalanced={true}
        onGridReady={onGridReady}
        onPaginationChanged={() => {
         if (gridApiRef.current) {
             const pageSize = gridApiRef.current.paginationGetPageSize();
             setPaginationPageSize(pageSize);
             }
           }}
        paginateChildRows={true}
        pagination={true}
        paginationPageSize={10}
        paginationPageSizeSelector={paginationPageSizeSelector}
        rowModelType="serverSide"
      />
    </TableContainer>
  </>
);
};

问题根源与解决方案

根源分析

服务端行分组模式下,开启groupAllowUnbalanced: true时,AG Grid会同时发起两个请求:

  1. 根分组数据请求(groupKeys为空数组)
  2. 未分组顶级数据请求(groupKeys[0] === "")
    加上你在根请求中修改fetchedDataRef.current触发的变更检测,会导致额外的重复调用。

解决方案

1. 合并根请求与未分组数据逻辑

去掉groupKeys[0] === ""的分支,将未分组数据逻辑整合到根请求中,避免重复API调用:

const getServerSideDatasource = (): IServerSideDatasource => {
  return {
    getRows: async (params: IServerSideGetRowsParams) => {
      console.log("Requesting rows", params.request);
      
      const newStartRow = params.request.startRow ?? 0;
      const newEndRow = params.request.endRow ?? 10;
      const campaignFilters = filtersRef.current;

      // 处理分组子节点请求
      if (params.request.groupKeys.length > 0 && params.request.groupKeys[0] !== "") {
        const campaign_id = params.request.groupKeys[0];
        try {
          const res = await fetchCampaignOverview({ startRow: newStartRow, endRow: newEndRow, campaign_id, ...campaignFilters });
          params.success({ 
            rowData: res.rows || [], 
            rowCount: res.totalRows || 0 
          });
        } catch (error) {
          console.error("Error fetching group rows:", error);
          params.fail();
        }
        return;
      }

      // 统一处理根分组数据
      try {
        const res = await fetchCampaignOverview({ startRow: newStartRow, endRow: newEndRow, ...campaignFilters });
        if (!res.rows.length) {
          params.success({ rowData: [], rowCount: 0 });
          return;
        }

        // 处理分组标记,无需存储到ref
        const processedData = res.rows.map(item => ({
          ...item,
          campaign_id: item.has_child ? item.campaign_id : ""
        }));
        
        // 根分组仅返回需要分组的节点,未分组数据由AG Grid自动处理
        const rootGroupData = processedData.filter(item => item.campaign_id !== "");
        params.success({ 
          rowData: rootGroupData, 
          rowCount: res.totalRows 
        });
      } catch (error) {
        console.error("Error fetching root rows:", error);
        params.fail();
      }
    },
  };
};

2. 优化过滤器更新逻辑

修正memoizedFilters的依赖,并添加purgeCache确保完全刷新:

// 直接拷贝filters对象作为memo值,避免JSON.stringify的不必要计算
const memoizedFilters = useMemo(() => ({...filters}), [filters]);

useEffect(() => {
  if (gridApiRef.current && !gridApiRef.current.isDestroyed()) {
    console.log("Refreshing server-side data with new filters");
    // 添加purgeCache清空缓存,避免旧数据触发额外请求
    gridApiRef.current.refreshServerSide({ purgeCache: true });
  }
}, [memoizedFilters]);

3. 调整分页配置(可选)

如果不需要子分组分页,关闭paginateChildRows减少额外请求:

<AgGridReact
  // ...其他配置
  paginateChildRows={false}
/>

内容的提问来源于stack exchange,提问作者dev here

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 10:14:53