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

异步逻辑异常:组件查询未完成时填充状态致数据异常

异步状态设置异常:首次导航页面时奖牌数据无法填充,刷新后正常

问题场景

视频查看器应用中,用户完成视频可获取铜、银、金牌。奖牌展示页在刷新后功能完全正常,但首次导航至该页面时,本地状态无数据。尽管设置状态前的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

问题根源与解决方法

问题根源

  1. setMedalData无需await:React状态更新函数是同步的,不返回Promise,加await完全无效。
  2. 两个useEffect冲突:空依赖的useEffect在组件挂载时执行,此时selectedChild可能未初始化完成,导致请求用了无效的childID,返回空数据;后续selectedChild更新触发另一个useEffect,但空数据的状态更新可能覆盖正确数据。
  3. setLoading(false)时机错误:直接放在useEffect中,未等待异步请求完成,导致页面提前渲染,使用了初始状态。
  4. 重复设置状态: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 16:00:19