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

React中useState setter未触发,useEffect状态不更新问题求助

问题描述

需求逻辑:

  1. 先拉取20分钟初始数据,将返回的最新时间戳存入latestTimeStamp状态
  2. 依赖latestTimeStamp触发第二个useEffect,每5秒从latestTimeStamp拉取到当前时间的新数据,更新latestTimeStamp以循环触发自身

实际异常:

  • 第一个useEffect中latestTimeStamp设置正常
  • 第二个useEffect的API每5秒正常调用,但标注的console.log("NEW TIMESTAMP VALUE IS --> ", ...)从未执行
  • latestTimeStamp状态始终无变化

相关代码:

const Roll: React.FC<RollProps> = ({ selectedTimeSpan }) => {

  const [chart, setChart] = useState<ChartXY | undefined>(undefined);
  const [latestTimeStamp, setLatestTimeStamp] = useState<string>('');

  useEffect(() => {
    const fetchData = async () => {
      
      const timeSpanString:string = selectedTimeSpan === 180 ? '?startTime=2023-09-17T07:05:00&endTime=2023-09-17T09:05:00' : '?startTime=2023-09-17T07:05:00&endTime=2023-09-17T07:25:00'

      try {
        // --> INITIAL FETCH TO GET 20 MINUTES OF DATA <--
        const response = await axios.get(`http://url/api?{timeSpanString}`);
        
        const chartData = response.data;

        // --> SET THE LATESTTIMESTAMP TO TRIGGER THE OTHER USEEFFECT.<--
        setLatestTimeStamp(chartData.rollData[chartData.rollData.length - 1].timestamp);

      } catch (error) {
        console.error("Unable to fetch data", error);
      }
      
    }

    fetchData();

  }, [chart, selectedTimeSpan]);

  // ****** useEffect for handling interval and adding data ****** //
  useEffect(() => {
    const intervalId = setInterval(async () => {
      try {
        console.log('First call on Interval: latestTimeStamp is:', latestTimeStamp);

        const timeSpanString: string = `?startTime=${latestTimeStamp}&endTime=${new Date().toISOString()}`;

        // --> FETCH DATA FROM LATESTTIMESTAMP TO NOW AND APPEND IT TO DATASET (all code is not shown, but not needed for this logic)...
        const response = await axios.get(`http://url/api?${timeSpanString}`);
        const newChartData: RollSet = response.data;

        // PS PS !! THIS CONSOLE.LOG is NEVER CALLED....
        console.log("NEW TIMESTAMP VALUE IS --> ", newChartData.rollData[newChartData.rollData.length - 1].timestamp)
        
        // --> UPDATE LATESTDATESTAMP EACH ROUNDTRIP <--
        setLatestTimeStamp(newChartData.rollData[newChartData.rollData.length - 1].timestamp);
        
      } catch (error) {
        console.error('Unable to fetch data', error);
      }
    }, 5000);

    // Cleanup interval on component unmount
    return () => clearInterval(intervalId);
  }, [latestTimeStamp]);

}
问题分析与解决方案

核心问题1:闭包导致的 stale state

第二个useEffect依赖latestTimeStamp,每次状态更新都会创建新的定时器,但定时器回调会捕获创建时的latestTimeStamp值。如果API请求耗时超过5秒,或者状态更新后旧定时器未及时清理,就会导致回调始终使用旧的状态值,无法正确更新。

核心问题2:API请求的潜在错误

标注的console.log未执行,说明try块在执行到该行前就触发了错误,大概率是newChartData.rollData为空数组或不存在,导致访问[newChartData.rollData.length - 1]时报错,直接进入catch块。

修正方案

1. 用useRef跟踪最新状态,避免闭包陷阱

使用useRef保存最新的latestTimeStamp,确保定时器回调能拿到实时值:

const latestTimeStampRef = useRef<string>('');

// 在设置状态时同步更新ref
setLatestTimeStamp(newVal => {
  latestTimeStampRef.current = newVal;
  return newVal;
});

2. 优化定时器逻辑,避免重复创建

改用单个定时器,仅在初始数据加载完成后启动,且确保前一次请求完成后再发起下一次:

const intervalIdRef = useRef<NodeJS.Timeout | null>(null);

// 初始数据加载的useEffect中,设置完latestTimeStamp后启动定时器
useEffect(() => {
  const fetchData = async () => {
    // ... 初始请求逻辑 ...
    const newTimeStamp = chartData.rollData[chartData.rollData.length - 1].timestamp;
    setLatestTimeStamp(newTimeStamp);
    latestTimeStampRef.current = newTimeStamp;
    
    // 启动定时器
    if (!intervalIdRef.current) {
      intervalIdRef.current = setInterval(fetchLatestData, 5000);
    }
  };

  fetchData();

  // 组件卸载时清理定时器
  return () => {
    if (intervalIdRef.current) {
      clearInterval(intervalIdRef.current);
    }
  };
}, [chart, selectedTimeSpan]);

// 单独提取获取最新数据的函数
const fetchLatestData = async () => {
  try {
    const currentTimeStamp = latestTimeStampRef.current;
    if (!currentTimeStamp) return;

    const timeSpanString = `?startTime=${currentTimeStamp}&endTime=${new Date().toISOString()}`;
    const response = await axios.get(`http://url/api${timeSpanString}`);
    const newChartData: RollSet = response.data;

    // 先校验数据结构
    if (!newChartData.rollData || newChartData.rollData.length === 0) {
      console.log("No new data received");
      return;
    }

    const newTimeStamp = newChartData.rollData[newChartData.rollData.length - 1].timestamp;
    console.log("NEW TIMESTAMP VALUE IS --> ", newTimeStamp);
    
    setLatestTimeStamp(newVal => {
      latestTimeStampRef.current = newTimeStamp;
      return newTimeStamp;
    });
  } catch (error) {
    console.error('Fetch latest data failed:', error);
  }
};

3. 修复初始请求的模板字符串错误

初始请求中的http://url/api?{timeSpanString}是语法错误,应该改为http://url/api${timeSpanString}(因为timeSpanString已经包含?前缀),否则会导致请求URL错误。

额外建议

  • 给latestTimeStamp设置合理的初始值(比如初始的结束时间),避免空值导致API请求参数无效
  • 在API请求前校验latestTimeStamp是否有效,避免无效请求
  • 添加请求防抖或节流,避免并发请求导致的状态冲突

内容的提问来源于stack exchange,提问作者Terje Nygård

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 09:35:58