React子组件在Props变更时未重新渲染的问题排查
问题:筛选数据后子组件未重新渲染
我开发了一个支持用户通过日期和搜索栏筛选数据的工具栏,筛选后的费用数据会传入子组件。但点击按钮变更Props后,子组件并未重新渲染,仍显示原始未筛选数据。
父组件代码
const Row1 = () => { const isAboveMediumScreen = useMediaQuery("(min-width: 1200px)"); let { data: expenses, isLoading } = useGetExpensesQuery(); const { data: categories } = useGetCategoriesQuery(); const [start, setStart] = React.useState<Dayjs | null>(null); const [end, setEnd] = React.useState<Dayjs | null>(null); const [category, setCategory] = React.useState<string>(""); useEffect(() => { setStart(getEarliest(expenses)); setEnd(getLatest(expenses)); setCategory("All categories"); }, [expenses]); const handleChange = (event: SelectChangeEvent<string>) => { setCategory(event.target.value); }; const handleFilter = () => { console.log(start, end, category); expenses = expenses?.filter((expense) => { let date = dayjs(expense.date); if (!date.isBefore(start) && !date.isAfter(end)) { return date; } }); console.log(expenses); }; if (!expenses) { return null; } return ( <LineChartComponent expenses={expenses} isLoading={isLoading} /> ); }; export default Row1;
子组件(LineChartComponent)代码
const LineChartComponent: React.FC<{ expenses: Expense[]; isLoading: boolean; }> = ({ expenses, isLoading }) => { const finalDate = useMemo(() => { console.log("line chart expenses", expenses); const finalDate: { date: string; expenses: number }[] = []; if (expenses) { const orderedDates = expenses .map((expense) => ({ ...expense, date: new Date(expense.date!).getMonth(), })) .sort((a, b) => a.date - b.date); const reduced = orderedDates.reduce( (map, { date, price, subCategory, userId, category }) => map.set(date, (map.get(date) ?? 0) + price), new Map() ); reduced.forEach((price, date) => { finalDate.push({ expenses: price, date: monthNames[date].substring(0, 3), }); }); return finalDate; } return []; }, [expenses]); const renderLegendText = (value: string) => { return <span>{value}($)</span>; }; return ( <ResponsiveContainer width="100%" height="100%"> <div style={{ display: "flex", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", }} > {isLoading ? ( <Typography variant="h4" sx={{ color: "#12efc8" }}> Loading... </Typography> ) : ( <LineChart width={400} height={230} data={finalDate} margin={{ // top: 5, right: 30, // left: 20, // bottom: 5, }} > <CartesianGrid strokeDasharray="3 3" /> <XAxis dataKey="date" /> <YAxis type="number" domain={["dataMin-100", "dataMax+100"]} /> <Tooltip /> <Legend formatter={renderLegendText} /> <Line type="monotone" dataKey="expenses" stroke="#12efc8" /> </LineChart> )} </div> </ResponsiveContainer> ); }; export default LineChartComponent;
我在LineChartComponent中使用useMemo避免重复执行相同逻辑,但不确定如何将useEffect与useMemo结合,考虑用useEffect检测Props变化,但不确定是否正确。
解决方案
核心问题
你直接修改了RTK Query返回的expenses变量(expenses = expenses?.filter(...)),但这个变量不是React状态,修改它不会触发组件重渲染,子组件也就无法获取到筛选后的新数据。
具体修复步骤
- 新增筛选结果状态:在父组件中添加
filteredExpenses状态,用于存储筛选后的数据,初始值设为原始expenses。 - 修正筛选逻辑:在
handleFilter中不再直接修改expenses,而是将筛选结果存入filteredExpenses状态,触发组件重渲染。 - 同步原始数据更新:当RTK Query返回的原始
expenses变化时,同步更新filteredExpenses,保证初始数据正确。 - 传递筛选后的数据:将传给子组件的
expenses替换为filteredExpenses。
修改后的父组件代码
const Row1 = () => { const isAboveMediumScreen = useMediaQuery("(min-width: 1200px)"); const { data: expenses, isLoading } = useGetExpensesQuery(); const { data: categories } = useGetCategoriesQuery(); const [start, setStart] = React.useState<Dayjs | null>(null); const [end, setEnd] = React.useState<Dayjs | null>(null); const [category, setCategory] = React.useState<string>(""); // 新增状态存储筛选后的数据 const [filteredExpenses, setFilteredExpenses] = React.useState<Expense[] | undefined>(expenses); useEffect(() => { if (expenses) { setStart(getEarliest(expenses)); setEnd(getLatest(expenses)); setCategory("All categories"); // 原始数据更新时同步筛选结果 setFilteredExpenses(expenses); } }, [expenses]); const handleChange = (event: SelectChangeEvent<string>) => { setCategory(event.target.value); }; const handleFilter = () => { console.log(start, end, category); if (!expenses || !start || !end) return; // 执行筛选(补充了分类筛选逻辑) const result = expenses.filter((expense) => { const date = dayjs(expense.date); const matchesCategory = category === "All categories" || expense.category === category; return !date.isBefore(start) && !date.isAfter(end) && matchesCategory; }); setFilteredExpenses(result); console.log(result); }; if (!expenses) { return null; } return ( <LineChartComponent expenses={filteredExpenses} isLoading={isLoading} /> ); }; export default Row1;
关于useMemo与useEffect的疑问
子组件中的useMemo已经正确依赖了expenses,只要父组件传递的expenses(现在是filteredExpenses)发生变化,useMemo就会自动重新计算结果,不需要额外用useEffect检测Props变化。你的useMemo用法是正确的,它已经起到了缓存计算结果、避免重复执行的作用。
内容的提问来源于stack exchange,提问作者Mathew
相关产品推荐
相关产品推荐

