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

使用react-query与react-hook-forms时,获取数据致表单短暂为空

问题描述

我正在开发一个SPA待办事项应用,使用react-hook-forms处理待办事项的创建与编辑,使用react-query获取待办数据。

用户点击“保存”后,路径从create-todo切换至todo/:id,同时使当前useGetTodo查询失效;路径变化时,useGetTodo会从服务器获取待办数据。

问题在于:切换路径并重取数据后,表单会短暂为空,直到useGetTodo获取完成后才重新填充(该待办是之前创建的,字段内容完全一致,本不应出现空表单情况)。

相关代码

表单初始化代码

const {todo} = useGetTodo();

const getDefaultValues = useCallback(() => {
  return { name: todo.name, isDone: todo.isDone } 
}, [todo]);

const form = useForm({
  mode: 'all',
  defaultValues: useMemo(getDefaultValues, [getDefaultValues]),
});

useEffect(() => {
  if (todo) {
    reset(getDefaultValues);
  }
}, [reset, todo, getDefaultValues]);

useGetTodo实现代码

const {data, ...rest} = useQuery(['todo', todoId], async () => {
  if (todoId) {
    return await getTodo(todoId)
  }
  return null;
})

react-query初始化代码

const queryClient = new QueryClient({
  defaultOptions: {
    suspense: false,
    keepPreviousData: true,
  }
})

保存时的mutation代码

const todoMutation = useMutation(
  ['todo'],
  getUpdatedTodo,
  {onSuccess: () => queryClient.invalidateQueries(['todo', todoId])}
);

问题原因及解决方案

1. 路由切换时新查询无缓存兜底

从create-todo切换到todo/:id时,新路由的todoId对应全新的查询键['todo', newId],keepPreviousData仅对同一查询键的分页/滚动场景生效,对不同键的查询不适用。此时新查询初始状态下data为undefined,todo为空,导致表单默认值无法获取有效数据,出现短暂空白。

解决办法:
给useQuery的返回数据设置兜底默认值,避免todo为null/undefined时表单字段报错或为空:

const {data: todo = { name: '', isDone: false }, ...rest} = useQuery(['todo', todoId], async () => {
  if (todoId) {
    return await getTodo(todoId)
  }
  return { name: '', isDone: false };
})

2. 表单默认值初始化时机过早

useMemo(getDefaultValues, [getDefaultValues])在组件挂载时立即执行,此时若todo还未加载完成,getDefaultValues会返回{ name: undefined, isDone: undefined },导致表单初始为空。后续todo加载完成后才触发useEffect中的reset,中间存在空白期。

解决办法:
将defaultValues设为延迟执行的函数,让useForm在需要时才获取最新的todo值,同时简化冗余代码:

const form = useForm({
  mode: 'all',
  defaultValues: () => ({ 
    name: todo?.name || '', 
    isDone: todo?.isDone || false 
  }),
});

// 可删除原有的getDefaultValues、useCallback、useMemo以及对应的useEffect

3. 失效查询未优先更新缓存

点击保存后直接执行invalidateQueries,会标记旧缓存为失效,新路由的查询需要重新请求服务器,等待期间表单无数据填充。

优化建议:
利用setQueryData在mutation成功后直接更新对应查询的缓存,让新路由的useGetTodo能立即拿到最新数据,再通过invalidateQueries做后台同步:

const todoMutation = useMutation(
  getUpdatedTodo,
  {
    onSuccess: (updatedTodo) => {
      // 先更新缓存,保证路由切换后能立即拿到数据
      queryClient.setQueryData(['todo', updatedTodo.id], updatedTodo);
      // 后台同步最新数据
      queryClient.invalidateQueries(['todo', updatedTodo.id]);
    }
  }
);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 01:54:55