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
相关产品推荐
相关产品推荐

