Search code examples
reactjsreduxreact-reduxredux-toolkit

How can I "group" redux store slices under the same property?


I'm very new to Redux / Redux toolkit, and I'm just trying to wrap my head around how to organize my store state. Basically I'm trying to group multiple slices of my store together but having issue getting typescript to accept it.

What I'm trying to acheive:

const fooSlice = createSlice(...);
const barSlice = createSlice(...);
const eggzSlize = createSlice(...);

const store = configureStore({
  reducer: {
    myGroupedSlice: {
      // Group these together under a property
      foo: fooSlice.reducer,
      bar: barSlice.reducer,
    },
    eggz: eggzSlize.reducer
  }
});

// Typed hooks - as per the docs
type RootState = ReturnType<typeof store.getState>;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
type AppDispatch = typeof store.dispatch;
export const useAppDispatch: () => AppDispatch = useDispatch;

// So I can then use the selectors as such
const barValue = useAppSelector((state) => state.myGroupedSlice.bar);
const eggzValue = useAppSelector((state) => state.eggz);

I have tried using combineReducers in multiple ways:

  • Using it as the value for myGroupedSlice like myGroupedSlice: combineReducers({ foo: fooSlice, bar: barSlice}). Typescript gives an error if I also add the eggz reducer alongside it, and in both cases the useAppSelector loses it's typing to any.
  • Using it to wrap everything like reducer: combineReducers({ myGroupedSlice: combineReducers(...), eggz: eggzSlice.reducer}). In this case, typescript doesn't error immediately, but the useAppSelector hook will lose it's type to any.

In the cases where Typescript technically allows it, I haven't actually tested to see if they're working even with malfunctioning type check.

Edit

As requested, I have created a sandbox: https://codesandbox.io/p/sandbox/damp-meadow-w5wrc6

While experimenting with the issue, I realised the problem is actually only ever present when I add a middleware to it; in this case, creating the store as such:

export const listenerMiddleware = createListenerMiddleware();

const store = configureStore({
  reducer: {
    myGroupedSlice: combineReducers({
      foo: fooSlice.reducer,
      bar: barSlice.reducer,
    }),
    eggz: eggzSlice.reducer,
  },
  /* 
  The middleware `getDefaultMiddleware` appears to type `myGroupedslice` as 
  `unknown`, leading to a conflict in the `myGroupedSlice` type
  */
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().prepend(listenerMiddleware.middleware),
});

Solution

  • I figured out the solution to the issue: pulling the "definition" of myGroupedSlice outside of the configureStore will evaluate the types correctly:

    const myGroupedSliceReducers = combineReducers({
      foo: fooSlice.reducer,
      bar: barSlice.reducer,
    });
    
    const store = configureStore({
      reducer: {
        myGroupedSlice: myGroupedSliceReducers,
        eggz: eggzSlice.reducer,
      },
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware().prepend(listenerMiddleware.middleware),
    });
    // No typescript errors and works correctly