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

React组件无限重渲染排查:触发筛选致更新深度超限

React组件无限重渲染(Maximum update depth exceeded)问题排查与修复

问题描述

开发DoctorTable组件用于渲染带数据的表格及筛选功能,触发onSearchSubmit、onSearchClear或onUpdateFilter时出现无限重渲染错误(Maximum update depth exceeded),尝试用useRef存储uiData未解决。组件代码如下:

const DoctorTable = ({ onChange, value }: RichDoctorSelectProps) => {
  const [allDataSources, setAllDataSources] = useState<
    { page: number; items: TDataSource }[]
  >([]);
  const [uiData, setUiData] = useState<UIData>({
    latitude,
    longitude,
    page: 1,
    search: undefined,
    specialityIds: [],
    city: undefined,
    zipcode: undefined,
    country: undefined,
  });

  const {
    items: dataSource,
    refetch: refetchDoctorProjections,
  } = useDoctorProjections({
    input: uiToInput(uiData),
    locale: Locale.DeDe,
  });

  const onUpdateFilter = (filters: {
    address?: TAddressValue;
    specialityIds?: string[];
  }) => {
    // filter logic here
    setUiData({
      ...uiData,
      ...updatedFilters,
      page: 1,
    });
  };
  const onSearchSubmit = (search: string) => {
    setUiData((prevUiData) => ({
      ...prevUiData,
      search,
      page: 1,
    }));
    // refetch data
  };
  const onSearchClear = () => {
    setUiData({
      ...uiData,
      search: undefined,
      page: 1,
    });
  };

  const noSearchAndNoFilters =
    !uiData.search &&
    !uiData.specialityIds &&
    !uiData.city &&
    !uiData.country &&
    !uiData.zipcode;
  const hasSearch = uiData.search;
  const hasFilters =
    (uiData.specialityIds || []).length > 0 ||
    uiData.city ||
    uiData.country ||
    uiData.zipcode;

  useEffect(() => {
    if (uiData.page >= 1 && dataSource.length > 0) {
      const isPageAdded = allDataSources.some(
        (pageData) => pageData.page === uiData.page,
      );
      if (hasFilters || hasSearch || noSearchAndNoFilters) {
        setAllDataSources((prevItems) => [
          { page: 1, items: dataSource },
        ]);
      } else if (!isPageAdded) {
        if (uiData.page === 1) {
          setAllDataSources([{ page: 1, items: dataSource }]);
        } else {
          setAllDataSources((prevItems) => [
            ...prevItems,
            { page: uiData.page, items: dataSource },
          ]);
        }
      }
    }
  }, [dataSource]);

  const table = (
    <Table
      value={selectedDoctor}
      dataSource={allDataSources.flatMap((pageData) => pageData.items)}
      isLoading={loading && uiData.page === 1}
      isCalled={called}
      onRowClick={onChange}
      hasMore={uiData.page < total}
      onLoadMore={() => setUiData({ ...uiData, page: uiData.page + 1 })}
    />
  );

  const filters = (
    <Filters
      searchValue={uiData.search}
      onSearchSubmit={onSearchSubmit}
      onSearchClear={onSearchClear}
      onUpdateFilter={onUpdateFilter}
    />
  );

  return (
    <div className={cn.root}>
      {filters}
      {table}
    </div>
  );
};

export { DoctorTable };

此外,表格组件位于处理无限滚动的包装组件内。


可能原因及修复方案

1. useEffect依赖缺失导致循环更新

你的useEffect仅声明了dataSource为依赖,但内部使用了uiData、allDataSources、hasFilters、hasSearch、noSearchAndNoFilters这些状态或衍生值。依赖不全时,React无法准确判断何时触发useEffect,导致每次组件重渲染时useEffect都可能执行,而执行setAllDataSources又会触发重渲染,形成无限循环。

修复:
补全依赖数组,或将衍生值移到useEffect内部避免依赖外部变量:

useEffect(() => {
  // 将衍生判断逻辑移到内部,减少外部依赖
  const noSearchAndNoFilters =
    !uiData.search &&
    !uiData.specialityIds &&
    !uiData.city &&
    !uiData.country &&
    !uiData.zipcode;
  const hasSearch = uiData.search;
  const hasFilters =
    (uiData.specialityIds || []).length > 0 ||
    uiData.city ||
    uiData.country ||
    uiData.zipcode;

  if (uiData.page >= 1 && dataSource.length > 0) {
    const isPageAdded = allDataSources.some(
      (pageData) => pageData.page === uiData.page,
    );
    if (hasFilters || hasSearch || noSearchAndNoFilters) {
      setAllDataSources((prevItems) => [
        { page: 1, items: dataSource },
      ]);
    } else if (!isPageAdded) {
      if (uiData.page === 1) {
        setAllDataSources([{ page: 1, items: dataSource }]);
      } else {
        setAllDataSources((prevItems) => [
          ...prevItems,
          { page: uiData.page, items: dataSource },
        ]);
      }
    }
  }
}, [dataSource, uiData, allDataSources]); // 补全必要依赖

2. 回调函数未缓存导致子组件频繁重渲染

onUpdateFilter、onSearchSubmit、onSearchClear每次组件渲染都会创建新的函数实例,如果Filters组件未做缓存处理,会引发子组件频繁重渲染,甚至意外触发回调函数,进而触发uiData更新形成循环。

修复:
用useCallback缓存回调函数,并给Filters组件添加React.memo包裹:

// 缓存回调函数
const onUpdateFilter = useCallback((filters: {
  address?: TAddressValue;
  specialityIds?: string[];
}) => {
  // 补充filter逻辑,生成updatedFilters
  const updatedFilters = {
    ...(filters.address ? {
      city: filters.address.city,
      country: filters.address.country,
      zipcode: filters.address.zipcode
    } : {}),
    specialityIds: filters.specialityIds || []
  };
  // 使用函数式更新获取最新状态
  setUiData(prev => ({
    ...prev,
    ...updatedFilters,
    page: 1,
  }));
}, []);

const onSearchSubmit = useCallback((search: string) => {
  setUiData((prevUiData) => ({
    ...prevUiData,
    search,
    page: 1,
  }));
}, []);

const onSearchClear = useCallback(() => {
  setUiData(prev => ({
    ...prev,
    search: undefined,
    page: 1,
  }));
}, []);

// 给Filters组件添加React.memo缓存
const Filters = React.memo((props) => {
  // 原Filters组件逻辑
});

3. useDoctorProjections的input参数不稳定

uiToInput(uiData)每次渲染都会返回新对象,即使uiData值未变化,也会导致useDoctorProjections认为参数更新,重新发起请求,获取新的dataSource后触发useEffect执行setAllDataSources,形成循环。

修复:
用useMemo缓存uiToInput的结果,确保参数稳定:

const input = useMemo(() => uiToInput(uiData), [uiData]);

const {
  items: dataSource,
  refetch: refetchDoctorProjections,
} = useDoctorProjections({
  input,
  locale: Locale.DeDe,
});

4. 状态更新未使用函数式更新导致闭包问题

onUpdateFilter和onSearchClear中直接使用...uiData更新状态,可能因为闭包捕获旧的uiData值,导致状态更新异常,引发不必要的重渲染循环。

修复:
统一使用函数式更新获取最新状态值:

// onUpdateFilter中
setUiData(prev => ({
  ...prev,
  ...updatedFilters,
  page: 1,
}));

// onSearchClear中
setUiData(prev => ({
  ...prev,
  search: undefined,
  page: 1,
}));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 15:59:53