RTK Query调用后用户状态未即时更新致路由重定向问题
问题描述
我正在开发一个基于JWT认证的应用,后端用Express,前端用React,通过react-router实现路由。需求是验证JWT令牌,验证通过后服务器返回数据,我用这些数据更新user状态,API调用用RTK Query实现。
当前问题:访问任意路由时,会先进入<PresistLogin />组件,它会调用useVerifyTokenQuery(),请求成功后应该dispatchsetUser(data)更新状态。但首次进入<RequiredAuth />组件时,获取的user状态是null,导致页面被重定向到/login路由;而进入/login路由时,再次经过<PresistLogin />和<RequiredAuth />,却能获取到正确的user状态。为什么首次访问时无法获取已更新的状态?这个问题导致了不必要的路由重定向。
应用路由
<Routes> <Route element={<PresistLogin />}> <Route element={<RequiredAuth />}> <Route path="/" element={<Main />}> <Route index element={<Home />} /> <Route path="apply"> <Route index element={<User.ApplyDoctor />} /> </Route> <Route element={<AdminOnly />}> <Route path="users"> <Route index element={<Admin.Users />} /> </Route> <Route path="doctors"> <Route index element={<Admin.Doctors />} /> </Route> </Route> </Route> <Route path="*" element={<div>404</div>} /> </Route> <Route element={<NotRequireAuth />}> <Route path="/login" element={<Login />} /> <Route path="/signup" element={<Signup />} /> </Route> </Route> </Routes>
verifyTokenQuery(RTK Query配置)
verifyToken: builder.query({ query() { return { url: 'verifyToken', method: 'GET', credentials: 'include', }; }, async onQueryStarted(args, { dispatch, queryFulfilled }) { try { const { data } = await queryFulfilled; const user = await cookieExtractor(data.data.accessToken); dispatch(setUser(user)); } catch (error) { console.log(error); } }, }),
PresistLogin组件
import React from 'react'; import { Outlet } from 'react-router-dom'; import { useVerifyTokenQuery } from '../../redux/api/authAPI'; import { Common } from '../index'; function PresistLogin() { const { data } = useVerifyTokenQuery(); console.log('🚀 ~ file: PresistLogin.jsx:8 ~ PresistLogin ~ data:', data); return !data ? <Common.LoaderOverlay /> : <Outlet />; } export default PresistLogin;
RequiredAuth组件
import React from 'react'; import { useSelector } from 'react-redux'; import { Navigate, Outlet, useLocation } from 'react-router-dom'; function RequiredAuth() { const user = useSelector((store) => store.userState.user); console.log('🚀 ~ file: RequiredAuth.jsx:7 ~ RequiredAuth ~ user:', user, Date.now()); const location = useLocation(); return user ? <Outlet /> : <Navigate to="/login" state={{ from: location }} replace />; } export default RequiredAuth;
userSlice(Redux Slice)
/* eslint-disable no-param-reassign */ import { createSlice } from '@reduxjs/toolkit'; const initialState = { user: null, notification: [], }; export const userSlice = createSlice({ name: 'userSlice', initialState, reducers: { logout: () => initialState, setUser: (state, action) => { state.user = action.payload; }, setNotification: (state, action) => { state.notification = action.payload; }, }, }); export const { logout, setUser, setNotification } = userSlice.actions; export default userSlice.reducer;
问题原因与解决方案
核心原因
- 状态更新时机滞后:
useVerifyTokenQuery返回data时,onQueryStarted里的dispatch(setUser)还未完成Redux状态更新,此时RequiredAuth组件已经渲染,读取到初始的null值,触发重定向。 - 缓存机制导致二次渲染差异:首次请求完成后RTK Query会缓存结果,第二次进入路由时
onQueryStarted会立即执行,user状态快速更新,因此能读取到正确值。 PresistLogin的判断逻辑不完整:仅判断data是否存在就渲染子路由,没有同步等待user状态完成更新。
解决方法
方法一:让PresistLogin等待user状态更新
修改PresistLogin,结合Redux的user状态判断,确保状态更新后再渲染子路由:
import React from 'react'; import { Outlet } from 'react-router-dom'; import { useVerifyTokenQuery } from '../../redux/api/authAPI'; import { useSelector } from 'react-redux'; import { Common } from '../index'; function PresistLogin() { const { data, isLoading } = useVerifyTokenQuery(); const user = useSelector((store) => store.userState.user); // 等待请求完成且user状态已更新 if (isLoading || (!user && data)) { return <Common.LoaderOverlay />; } return <Outlet />; } export default PresistLogin;
方法二:优化RTK Query的状态更新逻辑
将状态更新逻辑移到transformResponse,让数据处理和状态更新更同步,同时利用isSuccess判断请求完成:
verifyToken: builder.query({ query() { return { url: 'verifyToken', method: 'GET', credentials: 'include', }; }, async transformResponse(data) { return await cookieExtractor(data.data.accessToken); }, async onQueryStarted(args, { dispatch, queryFulfilled }) { try { const { data: user } = await queryFulfilled; dispatch(setUser(user)); } catch (error) { console.log(error); } }, }),
修改PresistLogin:
import React from 'react'; import { Outlet } from 'react-router-dom'; import { useVerifyTokenQuery } from '../../redux/api/authAPI'; import { Common } from '../index'; function PresistLogin() { const { isLoading, isSuccess } = useVerifyTokenQuery(); if (isLoading) { return <Common.LoaderOverlay />; } return <Outlet />; }
方法三:在RequiredAuth中处理加载状态
修改RequiredAuth,当请求还在加载时不立即重定向,避免过早跳转:
import React from 'react'; import { useSelector } from 'react-redux'; import { Navigate, Outlet, useLocation } from 'react-router-dom'; import { useVerifyTokenQuery } from '../../redux/api/authAPI'; import { Common } from '../index'; function RequiredAuth() { const user = useSelector((store) => store.userState.user); const { isLoading } = useVerifyTokenQuery(); const location = useLocation(); if (isLoading) { return <Common.LoaderOverlay />; } return user ? <Outlet /> : <Navigate to="/login" state={{ from: location }} replace />; } export default RequiredAuth;
内容的提问来源于stack exchange,提问作者BLIND
相关产品推荐
相关产品推荐

