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

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 />;
};

额外验证步骤

  1. 确认组件中selector路径和拼写正确,没有将state.roles误写为state.role
  2. 使用useEffect监听roles变化,确保拿到最新状态:
useEffect(() => {
  console.log('updated roles:', roles);
}, [roles]);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 20:23:16