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

CRA模板集成Redux-Saga等工具时useSelector报initialLoad属性未定义错误

报错问题

使用useSelector钩子获取数据时触发报错:Cannot read property 'initialLoad' of undefined,经排查是初始状态为undefined导致。

相关代码

selector.ts

import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'types';
import { initialState } from '.';

const selectSlice = (state: RootState) => state.initialLoad || initialState;
export const selectInitialLoad = createSelector([selectSlice], state => state);

index.ts

import { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from 'utils/@reduxjs/toolkit';
import { useInjectReducer, useInjectSaga } from 'utils/redux-injectors';
import { initialLoadSaga } from './saga';
import { InitialLoadState, RepoErrorType } from './types';

export const initialState: InitialLoadState = {
  name: 'initial-load',
  layouts: [],
  loading: false,
  error: null,
};

const slice = createSlice({
  name: 'initialLoad',
  initialState,
  reducers: {
    loadLayout(state) {
      state.loading = true;
      state.error = null;
      state.layouts = [];
    },
    layoutLoaded(state, action: PayloadAction<[]>) {
      const repos = action.payload;
      state.layouts = repos;
      state.loading = false;
    },
    layoutError(state, action: PayloadAction<string | RepoErrorType>) {
      state.error = 'error';
      state.loading = false;
    },
  },
});

export const { actions: appLayoutActions } = slice;

export const useAppLayoutSlice = () => {
  useInjectReducer({ key: slice.name, reducer: slice.reducer });
  useInjectSaga({ key: slice.name, saga: initialLoadSaga });
  return { actions: slice.actions };
};

types.ts

/* --- STATE --- */
export interface InitialLoadState {
  name: string;
  loading: boolean;
  error?: RepoErrorType | string | null;
  layouts: [];
}
export enum RepoErrorType {
  RESPONSE_ERROR = 1,
  USER_NOT_FOUND = 2,
  USERNAME_EMPTY = 3,
  USER_HAS_NO_REPO = 4,
  GITHUB_RATE_LIMIT = 5,
}

/* 
  If you want to use 'ContainerState' keyword everywhere in your feature folder, 
  instead of the 'InitialLoadState' keyword.
*/
export type ContainerState = InitialLoadState;

reducers.ts

/**
 * Combine all reducers in this file and export the combined reducers.
 */

import { combineReducers } from '@reduxjs/toolkit';

import { InjectedReducersType } from 'utils/types/injector-typings';

/**
 * Merges the main reducer with the router state and dynamically injected reducers
 */
export function createReducer(injectedReducers: InjectedReducersType = {}) {
  // Initially we don't have any injectedReducers, so returning identity function to avoid the error
  if (Object.keys(injectedReducers).length === 0) {
    return state => state;
  } else {
    return combineReducers({
      ...injectedReducers,
    });
  }
}

configureStore.ts

/**
 * Create the store with dynamic reducers
 */

import {
  configureStore,
  getDefaultMiddleware,
  StoreEnhancer,
} from '@reduxjs/toolkit';
import { createInjectorsEnhancer } from 'redux-injectors';
import createSagaMiddleware from 'redux-saga';

import { createReducer } from './reducers';

export function configureAppStore() {
  const reduxSagaMonitorOptions = {};
  const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
  const { run: runSaga } = sagaMiddleware;

  // Create the store with saga middleware
  const middlewares = [sagaMiddleware];

  const enhancers = [
    createInjectorsEnhancer({
      createReducer,
      runSaga,
    }),
  ] as StoreEnhancer[];

  const store = configureStore({
    reducer: createReducer(),
    middleware: [...getDefaultMiddleware(), ...middlewares],
    devTools: process.env.NODE_ENV !== 'production',
    enhancers,
  });

  return store;
}
临时解决方案

当前临时修复是在selector中增加state存在性判断:

import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'types';
import { initialState } from '.';

const selectSlice = (state: RootState) => state && state.initialLoad || initialState;
export const selectInitialLoad = createSelector([selectSlice], state => state);
根本原因

你使用的是动态注入reducer的架构,reducers.ts中定义的逻辑是:当没有任何注入的reducer时,返回state => state恒等函数作为根reducer。Redux初始化时会首次调用根reducer,传入的默认state为undefined,恒等函数直接返回undefined,导致store的根state初始值为undefined,此时selector中直接访问state.initialLoad就会触发属性读取报错。
未清理的CRA模板中默认存在提前注入的公共reducer(比如路由reducer、全局配置reducer等),初始化时injectedReducers不为空,会走combineReducers逻辑,combineReducers会自动生成初始的根state对象,不会出现根state为undefined的情况,所以运行正常。清理模板后你删除了所有默认提前注入的reducer,就触发了根state为undefined的问题。

正确修复方案
  • 优先修改公共reducer逻辑,从根源避免根state为undefined。将reducers.ts中的空reducer回退逻辑修改为默认返回空对象:
if (Object.keys(injectedReducers).length === 0) {
  return (state = {}) => state;
}

修改后无需改动selector代码,即可正常运行。

  • 如果不想修改公共逻辑,也可以将selector的取值改为可选链写法,代码更简洁规范:
const selectSlice = (state: RootState) => state?.initialLoad || initialState;
  • 确保reducer注入时机早于selector调用时机,在应用根组件中提前调用useAppLayoutSlice注入全局需要的reducer,不要在业务子组件中才调用注入逻辑,也可以避免该问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 06:09:03