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

MERN+Redux Toolkit项目获取JWT accessToken遇useSelector调用错误求助

解决Redux Toolkit中获取JWT Token时useSelector的调用错误

问题原因

useSelector是React Hook,必须遵循Hook调用规则:只能在React组件的渲染函数内部,或者自定义Hook中调用,不能在模块顶层(比如你当前的apiCalls全局代码)直接使用,这就是你遇到"React Hook cannot be called at the top level"错误的核心原因。

另外你的Redux状态中,accessToken存储在currentUser对象下,正确的获取路径应为state.user.currentUser?.accessToken(用可选链避免用户未登录时报错),而非state.user.accessToken。

下面提供几种可行的解决方案:


方案1:使用Axios请求拦截器(推荐)

通过Axios拦截器,每次发起请求时动态从Redux Store获取最新token,无需在组件中重复处理header逻辑:

修改apiCalls代码

import axios from 'axios';
import store from '../store'; // 导入你的Redux Store实例(根据实际路径调整)

const BASE_URL = "http://localhost:5000";

export const publicRequest = axios.create({
    baseURL: BASE_URL
});

export const userMethod = axios.create({
    baseURL: BASE_URL
});

// 添加请求拦截器,自动注入token
userMethod.interceptors.request.use((config) => {
    const currentUser = store.getState().user.currentUser;
    if (currentUser?.accessToken) {
        config.headers.token = `Bearer ${currentUser.accessToken}`;
    }
    return config;
});

优势

  • 拦截器自动在每个请求中注入最新token,无需重复编写header逻辑
  • 实时同步Redux Store中的token状态,状态更新后请求会自动使用新token

方案2:在React组件中动态获取token并传入请求

如果不想用拦截器,可以在组件内部合法调用useSelector拿到token,发起请求时手动设置headers:

组件示例代码

import { useSelector } from 'react-redux';
import { userMethod } from './apiCalls';

const ProfilePage = () => {
    // 在组件内部合法调用useSelector
    const token = useSelector(state => state.user.currentUser?.accessToken);

    const fetchUserProfile = async () => {
        try {
            const response = await userMethod.get('/api/user/profile', {
                headers: { token: `Bearer ${token}` }
            });
            console.log(response.data);
        } catch (error) {
            console.error('请求失败:', error);
        }
    };

    return (
        <div>
            <button onClick={fetchUserProfile}>获取个人信息</button>
        </div>
    );
};

export default ProfilePage;

方案3:结合LocalStorage持久化token(解决页面刷新丢失状态问题)

Redux Store状态存在内存中,页面刷新后会丢失,因此可以在登录成功时将token存入LocalStorage,拦截器从LocalStorage读取:

步骤1:修改userRedux的登录/登出逻辑

import { createSlice } from '@reduxjs/toolkit'

const userSlice = createSlice({
    name: "user",
    initialState: {
       currentUser: JSON.parse(localStorage.getItem('currentUser')) || null, // 刷新时从LocalStorage恢复状态
       isFetching: false,
       error: false
    },
    reducers: {
        loginStart: (state) => {
            state.isFetching = true;
        },
        loginSuccess: (state, action) => {
            state.isFetching = false;
            state.currentUser = action.payload;
            // 将用户信息存入LocalStorage
            localStorage.setItem('currentUser', JSON.stringify(action.payload));
        },
        loginFailure: (state) => {
            state.isFetching = false;
            state.error = true;
        },
        logout: (state) => {
            state.currentUser = null;
            // 登出时清除LocalStorage
            localStorage.removeItem('currentUser');
        }
    }
})

export const { loginStart, loginSuccess, loginFailure, logout } = userSlice.actions
export default userSlice.reducer;

步骤2:修改apiCalls的拦截器

import axios from 'axios';

const BASE_URL = "http://localhost:5000";

export const publicRequest = axios.create({
    baseURL: BASE_URL
});

export const userMethod = axios.create({
    baseURL: BASE_URL
});

userMethod.interceptors.request.use((config) => {
    const currentUser = JSON.parse(localStorage.getItem('currentUser'));
    if (currentUser?.accessToken) {
        config.headers.token = `Bearer ${currentUser.accessToken}`;
    }
    return config;
});

注意事项

  • LocalStorage存在XSS攻击风险,若token包含敏感信息,建议后端使用HttpOnly Cookie存储token
  • 登录/登出时要同步更新LocalStorage和Redux状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 21:20:22