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

React严格模式下接收正确数据仍返回400 Bad Request问题排查

问题原因与解决方案

核心原因

React Strict Mode在开发环境会刻意重复调用异步操作、副作用函数(如useEffect、事件触发的异步逻辑),目的是检测代码中的不稳定副作用。你的场景下,updateLeaderboard被触发了两次:

  • 第一次请求成功写入数据,后端返回201;
  • 第二次请求重复执行插入操作,触发后端的错误逻辑(比如数据库Schema对questionResultList中的playerId有唯一约束,或重复插入导致的验证失败),因此返回400。

验证步骤

  1. 前端:在updateQuestionLeaderboard action中添加请求次数打印,确认是否发送了两次请求:
    console.log('Sending request, count:', window.requestCount ? window.requestCount++ : window.requestCount = 1)
    
  2. 后端:在Controller的catch块中打印完整错误信息,明确报错原因:
    catch (error) {
      console.error('Full error details:', error); // 打印完整错误
      res.status(400).json({ message: error.message, stack: error.stack });
    }
    

解决方案

后端(根本解决,保证接口幂等性)

修改Controller逻辑,确保重复执行请求不会导致错误:

  1. 先检查当前题目对应的排行榜中是否已存在该玩家的记录,存在则更新分数,不存在再插入:
export const updateQuestionLeaderboard = async (req, res) => {
  const { leaderboardId } = req.params;
  const { questionIndex, playerId, playerPoints } = req.body;
  let leaderboard;

  try {
    leaderboard = await Leaderboard.findById(leaderboardId);
    const questionLeaderboard = leaderboard.questionLeaderboard[questionIndex];
    
    // 检查是否已有该玩家的记录
    const existingPlayer = questionLeaderboard.questionResultList.find(item => item.playerId === playerId);
    if (existingPlayer) {
      // 若存在则更新分数(根据需求调整逻辑)
      existingPlayer.playerPoints = playerPoints;
    } else {
      // 不存在则新增
      questionLeaderboard.questionResultList.push({ playerId, playerPoints });
    }

    const newLeaderboard = await leaderboard.save();
    console.log({ newLeaderboard });
    res.status(201).json(newLeaderboard);
  } catch (error) {
    console.error('Update error:', error);
    res.status(400).json({ message: error.message });
  }
};
  1. 检查Leaderboard Schema:如果questionResultList中的playerId设置了unique: true,可以移除该约束(如果允许同一玩家多次提交),或者保留约束并使用findOneAndUpdate配合upsert逻辑。

前端(兼容Strict Mode开发环境)

确保触发请求的逻辑不会重复执行:

  • 如果updateLeaderboard是由useEffect触发,添加依赖数组并确保依赖稳定;
  • 给请求添加防抖逻辑,避免短时间内重复触发:
// 自己实现防抖函数
const debounce = (func, delay) => {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
};

const debouncedUpdateLeaderboard = useCallback(debounce((data, id, score) => {
  // 原updateLeaderboard逻辑
}, 300), [dispatch, token]);

补充说明

React Strict Mode的重复执行仅存在于开发环境,生产环境不会有此行为,但保证接口的幂等性(重复调用无副作用)是后端接口的最佳实践,能避免各种异常场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 23:22:56