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

React组件报错:Rendered more hooks than during previous render排查

解决React中"Rendered more hooks than during the previous render"错误

我编写了一段从数据库获取数据、处理逻辑并通过Recharts展示的组件代码,因逻辑较为复杂,使用useMemo Hook后出现如下错误:

Uncaught Error: Rendered more hooks than during the previous render.

以下是出现错误的组件代码:

import { useGetExpensesQuery, useGetSubCategoriesQuery } from "@/state/api";
import { useMemo } from "react";
import { useCookies } from "react-cookie";
import {
  BarChart,
  Bar,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from "recharts";

const BarChartComponent = () => {
  const [cookies] = useCookies(["id"]);
  const { data: expenses, isLoading } = useGetExpensesQuery(cookies.id);
  const { data: subCategories } = useGetSubCategoriesQuery();

  if (!expenses || isLoading) {
    return <p>Loading...</p>;
  }

  const monthNames = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December",
  ];

  const newData = useMemo(() => {
    let newData: { date: number }[] = [];
    const months = expenses
      .map((expense) => ({
        ...expense,
        value: expense.price,
        date: new Date(expense.date ?? "").getMonth(),
      }))
      .sort((a, b) => a.date - b.date);
    const reducedMonths = months.reduce(
      (map, { userId, date, price, category, subCategory }) =>
        map.set(date, { ...map.get(date ?? 0), [subCategory]: price }),
      new Map()
    );

    for (let [key, value] of reducedMonths) {
      newData.push({ date: monthNames[key], ...value });
    }
    return newData;
  }, [expenses]);

  const color = [
    "red",
    "purple",
    "blue",
    "orange",
    "green",
    "black",
    "pink",
    "yellow",
  ];

  return (
    <ResponsiveContainer width="100%" height="100%">
      <BarChart
        width={500}
        height={300}
        data={newData}
        margin={{
          top: 20,
          right: 30,
          left: 20,
          bottom: 5,
        }}
      >
        <CartesianGrid strokeDasharray="3 3" />
        <XAxis dataKey="date" />
        <YAxis />
        <Tooltip />
        <Legend />
        {subCategories?.map(({ name }, idx) => (
          <Bar key={name} dataKey={name} stackId="a" fill={color[idx]} />
        ))}
      </BarChart>
    </ResponsiveContainer>
  );
};

export default BarChartComponent;

问题原因

React的Hook规则明确要求:所有Hook必须在组件的顶层作用域调用,不能放在条件判断、循环、嵌套函数或者可能提前return的代码之后。

你的代码中,useMemo是在if (!expenses || isLoading)的return语句之后调用的。当expenses未加载完成或处于加载状态时,组件会提前return,此时useMemo不会执行;而当数据加载完成后,useMemo会被执行。这就导致两次渲染中Hook的调用数量不一致,触发了"Rendered more hooks than during the previous render"错误。

解决方法

把useMemo移到组件的顶层作用域(条件判断之前),同时在useMemo内部处理expenses为undefined或加载中的情况,避免报错:

import { useGetExpensesQuery, useGetSubCategoriesQuery } from "@/state/api";
import { useMemo } from "react";
import { useCookies } from "react-cookie";
import {
  BarChart,
  Bar,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from "recharts";

const BarChartComponent = () => {
  const [cookies] = useCookies(["id"]);
  const { data: expenses, isLoading } = useGetExpensesQuery(cookies.id);
  const { data: subCategories } = useGetSubCategoriesQuery();

  const monthNames = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December",
  ];

  // 把useMemo移到顶层
  const newData = useMemo(() => {
    // 处理expenses未加载或为空的情况
    if (!expenses || isLoading) return [];
    
    let newData: { date: string }[] = [];
    const months = expenses
      .map((expense) => ({
        ...expense,
        value: expense.price,
        date: new Date(expense.date ?? "").getMonth(),
      }))
      .sort((a, b) => a.date - b.date);
    const reducedMonths = months.reduce(
      (map, { date, price, subCategory }) => {
        const existing = map.get(date ?? 0) || {};
        // 注意:如果同一个月同一个subCategory有多条数据,这里改成了累加,原代码是覆盖,可根据需求调整
        map.set(date, { ...existing, [subCategory]: (existing[subCategory] || 0) + price });
        return map;
      },
      new Map()
    );

    for (let [key, value] of reducedMonths) {
      newData.push({ date: monthNames[key], ...value });
    }
    return newData;
  }, [expenses, isLoading]);

  const color = [
    "red",
    "purple",
    "blue",
    "orange",
    "green",
    "black",
    "pink",
    "yellow",
  ];

  if (!expenses || isLoading) {
    return <p>Loading...</p>;
  }

  return (
    <ResponsiveContainer width="100%" height="100%">
      <BarChart
        width={500}
        height={300}
        data={newData}
        margin={{
          top: 20,
          right: 30,
          left: 20,
          bottom: 5,
        }}
      >
        <CartesianGrid strokeDasharray="3 3" />
        <XAxis dataKey="date" />
        <YAxis />
        <Tooltip />
        <Legend />
        {subCategories?.map(({ name }, idx) => (
          <Bar key={name} dataKey={name} stackId="a" fill={color[idx]} />
        ))}
      </BarChart>
    </ResponsiveContainer>
  );
};

export default BarChartComponent;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 18:32:03