Redux state与useSelector登录设置状态时返回undefined
解码JWT后dispatch角色到Redux Store,但useSelector返回undefined
我在应用登录时尝试解码JWT以获取用户角色和权限,能成功从token中拿到角色:
roles: [ "SUPER_ADMIN", "CUSTOMER_CARE" ]
但将其dispatch到Redux store后,在其他组件用useSelector获取时返回undefined,希望得到解决思路。
处理登录/解码的auth文件
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; import { BASE_API_URI } from '../../constants'; import jwtDecode from 'jwt-decode'; import { setRoles } from '../../store/roles/rolesSlice'; import { store } from '../../main'; import { Role } from '../../constants'; export const TagTypes = { User: 'User', }; const rawBaseQuery = fetchBaseQuery({ baseUrl: `${BASE_API_URI}/api/v1/`, headers: { Accept: "application/json" }, prepareHeaders: (headers) => { const token = localStorage.getItem("access_token"); if (token) { headers.set("Authorization", `Bearer ${token}`); // Decode token using jwt-decode and extract roles const decodedToken: any = jwtDecode(token); const roles: Role[] = decodedToken.roles; console.log('roles ', roles); store.dispatch(setRoles(roles)); } return headers; }, mode: "cors", }); const baseQuery = async (args: any, api: any, extraOptions: any): Promise<any> => { let result = await rawBaseQuery(args, api, extraOptions); if (result.error && result.error.status === 401) { window.localStorage.removeItem("access_token"); window.location.href = "/login"; } return result; }; export const apiService = createApi({ reducerPath: "api", baseQuery: baseQuery, tagTypes: [ TagTypes.User, ], endpoints: () => ({ // SEE SUBFOLDERS }), });
包含store的main文件
import React from 'react' import './main.scss' import { I18nextProvider } from 'react-i18next' import i18n from './services/i18n' import ReactDOM from 'react-dom/client' import './styles/index.scss' import "@xbooks/ui/style.css"; import Router from './features/router' import { Provider } from "react-redux"; import { configureStore } from "@reduxjs/toolkit"; import { apiService } from "./services/api"; import { setDefaultOptions } from "date-fns"; import { it } from 'date-fns/locale' import rootReducer from './store/rootReducer'; setDefaultOptions({ locale: it }) export const store = configureStore({ reducer: { // Add reducers here ...rootReducer, [apiService.reducerPath]: apiService.reducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(apiService.middleware), }); ReactDOM.createRoot(document.getElementById('root') as HTMLElement) .render( <Provider store={store}> <I18nextProvider i18n={i18n}> <Router /> </I18nextProvider> </Provider> )
rootReducer文件
import { combineReducers } from '@reduxjs/toolkit'; import rolesReducer from './roles/rolesSlice'; const rootReducer = combineReducers({ roles: rolesReducer, // other reducers can be added here }); export type RootState = ReturnType<typeof rootReducer>; export default rootReducer;
rolesReducer文件
import { createSlice } from '@reduxjs/toolkit'; const rolesSlice = createSlice({ name: 'roles', initialState: [] as string[], reducers: { setRoles: (state, action) => { return action.payload; }, }, }); export const { setRoles } = rolesSlice.actions; export default rolesSlice.reducer;
selector文件
import {RootState} from '../rootReducer'; export const selectRoles = (state: RootState) => state.roles;
组件中调用selector的代码
const roles = useSelector(selectRoles); console.log('roles selector ', roles);
问题分析与解决方案
核心问题:循环导入导致store未正确初始化
auth文件直接导入main中的store,而main又导入auth中的apiService,形成循环依赖。这会导致auth文件中的store可能在初始化完成前就被引用,store.dispatch无法正确执行,最终store中的roles状态未被更新。
解决方案1:使用RTK Query的api参数替代直接导入store
修改baseQuery逻辑,将JWT解码和dispatch移到baseQuery函数中,利用RTK Query提供的api.dispatch方法,避免循环导入:
// 修改auth文件中的rawBaseQuery和baseQuery const rawBaseQuery = fetchBaseQuery({ baseUrl: `${BASE_API_URI}/api/v1/`, headers: { Accept: "application/json" }, prepareHeaders: (headers) => { const token = localStorage.getItem("access_token"); if (token) { headers.set("Authorization", `Bearer ${token}`); } return headers; }, mode: "cors", }); const baseQuery = async (args: any, api: any, extraOptions: any): Promise<any> => { const token = localStorage.getItem("access_token"); if (token) { const decodedToken: any = jwtDecode(token); const roles: Role[] = decodedToken.roles; console.log('roles ', roles); // 使用api.dispatch替代直接导入store.dispatch api.dispatch(setRoles(roles)); } let result = await rawBaseQuery(args, api, extraOptions); if (result.error && result.error.status === 401) { window.localStorage.removeItem("access_token"); window.location.href = "/login"; } return result; };
同时删除auth文件中对store的导入:
// 移除这一行 import { store } from '../../main';
解决方案2:在应用初始化时主动解码token
可以在App组件的useEffect中直接解码token并dispatch,不依赖API请求时机,确保页面加载时就更新store:
// App.tsx import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { setRoles } from './store/roles/rolesSlice'; import jwtDecode from 'jwt-decode'; const App = () => { const dispatch = useDispatch(); useEffect(() => { const token = localStorage.getItem('access_token'); if (token) { const decodedToken = jwtDecode(token); dispatch(setRoles(decodedToken.roles)); } }, [dispatch]); return <Router />; };
额外验证步骤
- 确认组件中selector路径和拼写正确,没有将
state.roles误写为state.role - 使用
useEffect监听roles变化,确保拿到最新状态:
useEffect(() => { console.log('updated roles:', roles); }, [roles]);
内容的提问来源于stack exchange,提问作者Asher
相关产品推荐
相关产品推荐

