异步逻辑异常:组件查询未完成时填充状态致数据异常
异步状态设置异常:首次导航页面时奖牌数据无法填充,刷新后正常
问题场景
视频查看器应用中,用户完成视频可获取铜、银、金牌。奖牌展示页在刷新后功能完全正常,但首次导航至该页面时,本地状态无数据。尽管设置状态前的console.log先于使用状态的代码触发,但状态在被使用时却被清空。
现有代码片段
获取奖牌数据的函数
// Grabs Medals async function getChildsMedals(){ return await client.query({ query: GET_CHILD_VIDEO_STATISTICS, fetchPolicy: 'network-only', variables: { childID: selectedChild.id } }).then( async (resolved) => { console.log("IN THE ASYNC") await setMedalData(resolved.data.getChildVideoStatistics.allTimeStats.individualVideoDetailedStats) return (resolved.data.getChildVideoStatistics.allTimeStats.individualVideoDetailedStats) }).catch(err => console.log(err)) }
注:曾尝试在return前加await确保状态设置,但无效;console.log("IN THE ASYNC")先于其他日志触发,说明该逻辑优先执行。
本地状态定义
const [medalData, setMedalData] = useState({ step_up: {}, toe_walking: {}, toe_touches: {}, squat: {}, side_to_side: {}, rolling: {}, leg_lifts: {}, hands_to_knees: {}, chair_elevation: {}, floor_to_stand: {}, beam_balancing: {}, jump_rope: {}, jumping_jacks: {}, jump_forward_and_backward: {}, hop_scotch: {}, bear_crawl: {} })
selectedChild状态用于跟踪当前查看奖牌的儿童,其值始终符合预期。
触发状态填充的useEffect
// Populates earned medals useEffect(() => { getChildsMedals() .then( (resolved) => { console.log("AFTER THE ASYNC") asyncSetMedalData(resolved) }) setLoading(false) }, [selectedChild]) // Populates earned medals unless async wants to keep being a fucking turd useEffect(() => { getChildsMedals() .then( (resolved) => { console.log("AFTER THE ASYNC") asyncSetMedalData(resolved) }) setLoading(false) }, [])
控制台日志对比
首次进入页面时
leg_lifts LOG {"beam_balancing": {}, "bear_crawl": {}, "chair_elevation": {}, "floor_to_stand": {}, "hands_to_knees": {}, "hop_scotch": {}, "jump_forward_and_backward": {}, "jump_rope": {}, "jumping_jacks": {}, "leg_lifts": {}, "rolling": {}, "side_to_side": {}, "squat": {}, "step_up": {}, "toe_touches": {}, "toe_walking": {}} LOG {} LOG undefined
页面重渲染(编辑器保存)时
LOG leg_lifts LOG {"leg_lifts": {"bronze": 2, "gold": 0, "silver": 1}} LOG {"bronze": 2, "gold": 0, "silver": 1} LOG 0
问题根源与解决方法
问题根源
setMedalData无需await:React状态更新函数是同步的,不返回Promise,加await完全无效。- 两个useEffect冲突:空依赖的useEffect在组件挂载时执行,此时
selectedChild可能未初始化完成,导致请求用了无效的childID,返回空数据;后续selectedChild更新触发另一个useEffect,但空数据的状态更新可能覆盖正确数据。 setLoading(false)时机错误:直接放在useEffect中,未等待异步请求完成,导致页面提前渲染,使用了初始状态。- 重复设置状态:
getChildsMedals函数内直接调用setMedalData,同时useEffect的then里又调用asyncSetMedalData,重复操作可能导致状态覆盖。
修复步骤
1. 清理getChildsMedals函数
让函数只负责获取数据,不再内部设置状态,避免重复操作:
// 获取奖牌数据 async function getChildsMedals() { try { const resolved = await client.query({ query: GET_CHILD_VIDEO_STATISTICS, fetchPolicy: 'network-only', variables: { childID: selectedChild.id } }); console.log("IN THE ASYNC"); return resolved.data.getChildVideoStatistics.allTimeStats.individualVideoDetailedStats; } catch (err) { console.log(err); return null; // 出错时返回null,避免后续报错 } }
2. 合并并修正useEffect
只保留依赖selectedChild的useEffect,等待selectedChild初始化完成后再发起请求,同时正确控制加载状态:
// 填充已获得的奖牌数据 useEffect(() => { // 若selectedChild未初始化,直接返回 if (!selectedChild?.id) return; const fetchMedals = async () => { setLoading(true); // 请求开始时开启加载状态 const medalStats = await getChildsMedals(); if (medalStats) { setMedalData(medalStats); } setLoading(false); // 请求完成后关闭加载 }; fetchMedals(); }, [selectedChild]);
3. 移除冗余的asyncSetMedalData
如果asyncSetMedalData只是重复调用setMedalData,直接删除该函数,避免重复设置状态导致的冲突。
4. 确保selectedChild初始化完成
如果selectedChild是从路由或父组件传递而来,需确保其在组件挂载时已有有效值,上述useEffect中的if (!selectedChild?.id) return已处理该情况。
内容的提问来源于stack exchange,提问作者user18899735
相关产品推荐
相关产品推荐

