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

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状态,修改它不会触发组件重渲染,子组件也就无法获取到筛选后的新数据。

具体修复步骤

  1. 新增筛选结果状态:在父组件中添加filteredExpenses状态,用于存储筛选后的数据,初始值设为原始expenses。
  2. 修正筛选逻辑:在handleFilter中不再直接修改expenses,而是将筛选结果存入filteredExpenses状态,触发组件重渲染。
  3. 同步原始数据更新:当RTK Query返回的原始expenses变化时,同步更新filteredExpenses,保证初始数据正确。
  4. 传递筛选后的数据:将传给子组件的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 00:05:10