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

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;

问题原因与解决方案

核心原因

  1. 状态更新时机滞后:useVerifyTokenQuery返回data时,onQueryStarted里的dispatch(setUser)还未完成Redux状态更新,此时RequiredAuth组件已经渲染,读取到初始的null值,触发重定向。
  2. 缓存机制导致二次渲染差异:首次请求完成后RTK Query会缓存结果,第二次进入路由时onQueryStarted会立即执行,user状态快速更新,因此能读取到正确值。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 04:37:09