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

调用setState后组件未渲染:React自定义Hook问题排查

问题:调用计数器操作方法后Cards组件未重新渲染,添加依赖项引发无限循环

我实现了一个管理计数器列表的自定义Hook useCounters,包含addCounter、removeCounter、updateCounter等操作方法,同时有一个Cards组件负责渲染这些计数器。当前遇到两个问题:

  1. 调用操作方法后,Hook内部的counters状态已更新,但Cards组件并未重新渲染;
  2. 尝试将counters作为初始加载数据的useEffect依赖项时,引发了无限渲染循环。

代码片段

useCounters Hook

import { useEffect, useState } from "react";
import { db } from "../services/database";

const useCounters = () => {
  const [counters, setCounters] = useState([]);
  console.log("Hook", counters);

  const addCounter = async (newCounter) => {
    try {
      await db.cards.add(newCounter);
      setCounters((prevState) => [...prevState, newCounter]);
    } catch (error) {
      console.error(error);
    }
  };

  const removeCounter = async (idToRemove) => {
    try {
      await db.cards.delete(idToRemove);
      setCounters(counters.filter((counter) => counter.id !== idToRemove));
    } catch (error) {
      console.error(error);
    }
  };

  const updateCounter = async (idToUpdate, newValue) => {
    try {
      await db.cards.update(idToUpdate, { value: newValue });
    } catch (error) {
      console.error(error);
    }
  };

  useEffect(() => {
    const loadData = async () => {
      db.cards.toArray().then((cards) => {
        setCounters(cards);
      });
    };
    loadData();
  }, []);

  return {
    counters,
    addCounter,
    removeCounter,
    updateCounter,
  };
};

export default useCounters;

Cards组件

import Counter from "../components/Counter";
import AddCounter from "../components/AddCounter.js";
import Sleep from "../components/Sleep";

import useCounters from "../Hooks/useCounters";

import Style from "../css/pages/cards.module.css";

const Cards = () => {
  const { counters } = useCounters();
  console.log("Cards", counters);

  return (
    <div className={Style.cards}>
      {counters.map((item) => (
        <Counter
          key={item.id}
          id={item.id}
          title={item.title}
          initial={item.initialValue}
          value={item.value}
          color={item.color}
        />
      ))}
      <AddCounter />
      <Sleep />
    </div>
  );
};

export default Cards;

问题原因分析

  1. updateCounter未同步更新本地状态:该方法仅更新了数据库,未调用setCounters修改本地的counters状态,导致组件无法感知到数据变化,不会触发重新渲染。
  2. removeCounter依赖闭包捕获的旧状态:直接使用当前作用域的counters数组进行过滤,而不是通过函数式更新获取最新状态,当闭包捕获的是旧数组时,可能导致状态更新不及时或与数据库数据不一致。
  3. 添加counters到useEffect依赖引发无限循环:若将counters加入初始加载的useEffect依赖数组,每次setCounters更新状态后,useEffect会重新执行,再次从数据库拉取数据并更新状态,形成循环。

修复方案

1. 完善updateCounter,同步更新本地状态

修改updateCounter方法,在更新数据库后,通过函数式更新本地状态,确保拿到最新的状态快照:

const updateCounter = async (idToUpdate, newValue) => {
  try {
    await db.cards.update(idToUpdate, { value: newValue });
    // 同步更新本地状态
    setCounters(prevState => 
      prevState.map(counter => 
        counter.id === idToUpdate ? {...counter, value: newValue} : counter
      )
    );
  } catch (error) {
    console.error(error);
  }
};

2. 用函数式更新优化removeCounter

改用函数式更新获取最新状态,避免闭包导致的旧状态问题:

const removeCounter = async (idToRemove) => {
  try {
    await db.cards.delete(idToRemove);
    // 通过函数式更新获取最新的prevState
    setCounters(prevState => prevState.filter(counter => counter.id !== idToRemove));
  } catch (error) {
    console.error(error);
  }
};

3. 保留初始加载useEffect的空依赖数组,添加卸载清理

初始加载只需要在组件挂载时执行一次,因此依赖数组保持为空。同时添加清理函数,避免组件卸载后执行setCounters导致内存泄漏:

useEffect(() => {
  let isMounted = true;
  const loadData = async () => {
    const cards = await db.cards.toArray();
    if (isMounted) {
      setCounters(cards);
    }
  };
  loadData();
  // 组件卸载时标记为已卸载
  return () => {
    isMounted = false;
  };
}, []);

4. 确认AddCounter组件正确调用addCounter

确保AddCounter组件正确从useCounters Hook获取并调用addCounter方法(示例如下):

// AddCounter组件示例
import useCounters from "../Hooks/useCounters";

const AddCounter = () => {
  const { addCounter } = useCounters();
  
  const handleAdd = () => {
    const newCounter = {
      id: Date.now(), // 可根据数据库规则调整ID生成方式
      title: "新计数器",
      initialValue: 0,
      value: 0,
      color: "#333"
    };
    addCounter(newCounter);
  };

  return <button onClick={handleAdd}>添加计数器</button>;
};

export default AddCounter;

完成以上修改后,所有操作方法都会正确同步更新本地状态,触发Cards组件重新渲染,同时不会引发无限循环问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 09:22:02