React Query:如何确定哪个mutation触发了query的重新获取
解决React Query中按钮加载状态互相干扰的问题
针对你遇到的所有按钮共享useIsFetching状态导致加载状态混乱的问题,这里有几个实用的解决方案:
方案1:乐观更新+mutation自身状态(最推荐)
直接给每个mutation加上乐观更新逻辑,UI会即时响应操作,无需等待query重新获取完成,按钮的加载状态只依赖对应mutation的isLoading,自然不会互相干扰。
示例代码(删除操作):
const queryClient = useQueryClient() const deleteTodoMutation = useMutation({ mutationFn: (todoId) => api.deleteTodo(todoId), // 乐观更新:先修改本地缓存 onMutate: async (todoId) => { // 取消当前正在进行的todos查询,避免覆盖乐观更新结果 await queryClient.cancelQueries({ queryKey: ['todos'] }) const previousTodos = queryClient.getQueryData(['todos']) // 从缓存中移除目标待办项 queryClient.setQueryData(['todos'], old => old.filter(todo => todo.id !== todoId) ) // 返回回滚函数,用于mutation失败时恢复缓存 return { previousTodos } }, // mutation失败时回滚缓存 onError: (err, todoId, context) => { queryClient.setQueryData(['todos'], context.previousTodos) }, // 最终同步服务器数据(可选,确保缓存与后端一致) onSettled: () => { queryClient.invalidateQueries({ queryKey: ['todos'] }) } })
删除按钮的状态控制:
<button onClick={() => deleteTodoMutation.mutate(todo.id)} disabled={deleteTodoMutation.isLoading} > {deleteTodoMutation.isLoading ? <Spinner /> : '删除'} </button>
这种方式既解决了加载状态的问题,又提升了用户体验,因为UI会立刻响应操作。
方案2:给每个按钮维护独立的加载状态(适合不使用乐观更新的场景)
如果必须等待query重新获取完成才更新UI,可以给每个按钮单独维护一个本地状态,结合mutation和query的状态来控制:
示例代码(删除操作):
import { useState, useEffect } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' const [isDeleteLoading, setIsDeleteLoading] = useState(false) const queryClient = useQueryClient() const deleteTodoMutation = useMutation({ mutationFn: (todoId) => api.deleteTodo(todoId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['todos'] }) } }) const todosQuery = useQuery({ queryKey: ['todos'], queryFn: api.getTodos, onSuccess: () => { // query获取完成后,关闭删除按钮的加载状态 if (deleteTodoMutation.status === 'success') { setIsDeleteLoading(false) // 重置mutation状态,避免后续其他操作干扰 deleteTodoMutation.reset() } } }) const handleDelete = (todoId) => { setIsDeleteLoading(true) deleteTodoMutation.mutate(todoId) }
按钮状态控制:
<button onClick={() => handleDelete(todo.id)} disabled={isDeleteLoading || deleteTodoMutation.isLoading} > {isDeleteLoading || deleteTodoMutation.isLoading ? <Spinner /> : '删除'} </button>
创建、更新按钮同理,各自维护独立的isCreateLoading、isUpdateLoading状态即可。
方案3:useIsFetching结合mutation状态过滤
这种方式不需要额外的本地状态,通过判断mutation的状态来过滤query的fetch状态:
import { useQuery, useMutation, useQueryClient, useIsFetching } from '@tanstack/react-query' import { useEffect } from 'react' const queryClient = useQueryClient() const todosQueryKey = ['todos'] const deleteTodoMutation = useMutation({ mutationFn: (todoId) => api.deleteTodo(todoId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: todosQueryKey }) } }) // 只监听todos列表的请求状态 const isTodosFetching = useIsFetching({ queryKey: todosQueryKey }) // 只有当前删除mutation执行中,或刚成功且query正在获取时,才显示加载 const isDeleteLoading = deleteTodoMutation.isLoading || (deleteTodoMutation.isSuccess && isTodosFetching > 0) // query获取完成后重置mutation状态,避免后续操作干扰 useEffect(() => { if (isTodosFetching === 0 && deleteTodoMutation.isSuccess) { deleteTodoMutation.reset() } }, [isTodosFetching, deleteTodoMutation])
按钮状态控制只需判断isDeleteLoading即可,逻辑和前两种方案一致。
内容的提问来源于stack exchange,提问作者DariusW
相关产品推荐
相关产品推荐

