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

React Query乐观更新时获取MongoDB生成ID的问题及优化咨询

解决方案:用临时ID实现乐观更新+避免重复创建数据

你当前的问题核心有两个:一是onMutate里手动调用了addNewHabit,导致mutation本身执行时又发了一次请求,所以重复创建了数据;二是乐观更新时拿不到真实ID,导致跳转路径异常。可以通过临时ID占位+请求成功后替换的方式解决:

步骤1:生成临时ID用于乐观更新

不用提前调用接口,先创建一个临时ID(比如用crypto.randomUUID()浏览器原生支持,或者用uuid库),把带临时ID的新habit先写入缓存,实现即时的乐观更新效果。

步骤2:移除onMutate里的重复请求

原来的onMutate里调用addNewHabit是多余的,因为React Query的mutation本身会自动执行这个函数,这就是导致重复创建数据的原因。

步骤3:请求成功后替换真实ID并跳转

在onSuccess回调里拿到接口返回的真实_id,替换缓存里对应临时ID的条目,然后再跳转到详情页,这时路径就不会是undefined了。

修改后的完整代码

// 可选择引入uuid库,或用浏览器原生的crypto.randomUUID()
import { v4 as uuidv4 } from 'uuid';

const addNewHabit = async ({ userId, habitName, getFullYear }) => {
  try {
    const { data } = await axios.post(import.meta.env.VITE_API_URL, {
      userId,
      habitName,
      getFullYear,
    });
    return data; // 返回完整数据,方便后续获取真实_id
  } catch (error) {
    console.log(error.message);
    throw error; // 抛出错误,让React Query的onError能正常捕获
  }
};

export const addHabitData = () => {
  const queryClient = useQueryClient();
  const navigate = useNavigate(); // 假设使用react-router的导航钩子

  return useMutation(addNewHabit, {
    onMutate: async (newHabit) => {
      await queryClient.cancelQueries("habits");
      const previousHabitsData = queryClient.getQueryData("habits");

      // 生成临时ID用于乐观更新占位
      const tempId = uuidv4();
      // 把带临时ID的新habit写入缓存
      queryClient.setQueryData("habits", (oldQueryData) => {
        return [...oldQueryData, { ...newHabit, _id: tempId }];
      });

      // 返回上下文,供后续错误处理使用
      return { previousHabitsData, tempId };
    },
    onSuccess: (responseData, _, context) => {
      const { tempId } = context;
      // 用真实ID替换缓存里的临时ID条目
      queryClient.setQueryData("habits", (oldQueryData) => {
        return oldQueryData.map(habit => 
          habit._id === tempId ? { ...habit, _id: responseData._id } : habit
        );
      });
      // 拿到真实ID后再跳转到详情页
      navigate(`/habits/${responseData._id}`);
    },
    onError: (_, __, context) => {
      // 请求失败时回滚缓存到之前的状态
      queryClient.setQueryData("habits", context.previousHabitsData);
    },
    onSettled: () => {
      queryClient.invalidateQueries("habits");
    },
  });
};

额外说明

  • 若不想引入uuid库,现代浏览器可直接使用crypto.randomUUID()生成临时ID,兼容性覆盖绝大多数场景。
  • 必须在addNewHabit中抛出错误,这样React Query才能触发onError回调完成缓存回滚。
  • 导航操作放在onSuccess中,确保只有请求成功时才跳转,避免错误场景下的无效跳转。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 13:20:19