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
相关产品推荐
相关产品推荐

