React中useState setter未触发,useEffect状态不更新问题求助
问题描述
需求逻辑:
- 先拉取20分钟初始数据,将返回的最新时间戳存入
latestTimeStamp状态 - 依赖
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
相关产品推荐
相关产品推荐

