TanStack Query Auth上下文登录/登出后UI不更新问题求助
解决TanStack Query登录/登出后UI不更新的问题
核心问题分析
登出后调用invalidateQueries会触发fetchUser重新请求,但此时用户已登出,请求会失败。TanStack Query默认会保留之前的缓存数据,导致data仍为登录状态的用户信息,isLogged始终为true,UI无法同步更新。登录时同时调用setQueryData和invalidateQueries会导致重复请求,甚至可能因请求失败覆盖正确的缓存数据。
具体修复方案
替换全局
queryClient为组件内钩子获取
在组件内使用useQueryClient钩子获取实例,避免全局实例可能导致的状态不同步问题。优化登录逻辑
登录成功后直接用返回的用户数据更新缓存,无需调用invalidateQueries(返回的用户数据已是最新状态)。修改登出逻辑
登出时直接移除用户查询的缓存,而非标记为stale后重新请求,确保data立即变为undefined,isLogged同步更新为false。处理未登录时的请求错误
为useQuery添加错误处理,当未登录导致请求失败时,主动清空缓存数据;同时关闭重试机制,避免无效请求。
修改后的关键代码片段
import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query'; // ...其他导入 const URL = "http://localhost:3000"; const fetchUser = async () => { const response = await axios.get(`${URL}/user/profile`, { withCredentials: true, }); return response.data; }; // 全局创建QueryClient(保持不变) const queryClient = new QueryClient(); function AuthContextWithQuery({ children }: PropsContext) { const [displayError, setDisplayError] = useState<{ username?: string; password?: string; }>({}); const navigate = useNavigate(); // 组件内获取queryClient实例 const queryClient = useQueryClient(); const { data, isLoading } = useQuery<Credentials>({ queryKey: ["user"], queryFn: fetchUser, staleTime: 1000 * 60 * 5, retry: 0, // 关闭重试,避免未登录时重复请求 onError: () => { // 请求失败时清空缓存,确保isLogged同步为false queryClient.setQueryData(["user"], undefined); }, }); const resetErrors = (delay: number) => { setTimeout(() => { setDisplayError({}); }, delay); }; // 登录Mutation const loginMutation = useMutation({ // 修正返回类型为Credentials,确保返回用户数据 mutationFn: async ({ username, password }: User): Promise<Credentials> => { const response = await axios.post( `${URL}/user/login`, { username, password }, { withCredentials: true }, ); return response.data; }, onSuccess: (response) => { // 直接用返回的用户数据更新缓存 queryClient.setQueryData(["user"], response); navigate("/"); }, onError: (err) => { const error = err as AxiosError<{ username?: string; password?: string; }>; if (error.response?.status === 400) { const errorMessage = error.response?.data; setDisplayError({ username: errorMessage?.username, password: errorMessage?.password, }); } resetErrors(4000); }, }); // 登出Mutation const logoutMutation = useMutation({ mutationFn: async (): Promise<void> => { await axios.post(`${URL}/user/logout`, {}, { withCredentials: true }); }, onSuccess: () => { // 移除用户查询的缓存,直接让data变为undefined queryClient.removeQueries({ queryKey: ["user"] }); navigate("/auth"); }, }); // ...注册Mutation保持不变 return ( <AuthQueryContext.Provider value={{ data, isLoading, login: loginMutation.mutateAsync, register: registerMutation.mutateAsync, logout: logoutMutation.mutateAsync, setDisplayError, displayError, resetErrors, isLogged: !!data, }} > {children} </AuthQueryContext.Provider> ); } export default AuthContextWithQuery;
额外说明
- 登录时无需调用
invalidateQueries,因为登录接口返回的用户数据已经是最新的,直接更新缓存即可让UI同步。 - 登出时使用
removeQueries彻底清除缓存,比setQueryData(null)更直接,避免后续无效请求影响状态。 - 关闭
useQuery的重试机制,减少未登录时的冗余请求,同时在错误回调中主动清空缓存,确保认证状态正确同步。
内容的提问来源于stack exchange,提问作者Matteo
相关产品推荐
相关产品推荐

