Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type definitions for Redux (Toolkit) store with preloadedState

I'm trying to make typings work for configuring a Redux store with a preloaded state.

The Redux Toolkit TypeScript quick start guide has this example:

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({
  reducer: {
    one: oneSlice.reducer,
    two: twoSlice.reducer
  }
})

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

Unfortunately with a preloaded state, it looks more like this:

export function initStore(preloadedState) {
  const store = configureStore({
    reducer: {
      one: oneSlice.reducer,
      two: twoSlice.reducer
    },
    preloadedState,
  })

  return store
}

From where do I now get the RootState type and the AppDispatch type?

like image 432
Zardoz Avatar asked Dec 22 '22 15:12

Zardoz


1 Answers

State from Reducer

You can infer the type of your state based on the argument types of your reducers. We will want to separate the reducer value into a separate const in order to use typeof on just the reducer.

const reducer = {
  one: oneSlice.reducer,
  two: twoSlice.reducer
};

You are using an object of slice reducers rather than a function created combineReducers. Redux toolkit exports a utility type that we can use to infer the state from reducer map object notation.

import { StateFromReducersMapObject } from "@reduxjs/toolkit";

export type RootState = StateFromReducersMapObject<typeof reducer>

Return Types

We could have just as well gotten the type for the Store by looking at the ReturnType of initStore and then gotten the RootState by looking at the ReturnType from the store's getState method. That would be the most similar to the example. This same approach also allows us to get the type for AppDispatch. Note that we use bracket notation instead of dot notation because our Store is a type, not an object.

type Store = ReturnType<typeof initStore>
type RootState = ReturnType<Store['getState']>
type AppDispatch = Store['dispatch']

PreloadedState Type

The advantage of separating the reducer outside of initStore is that we can now use the types from the reducer to declare the appropriate type for the preloadedState argument, which was not typed before.

import { configureStore, Slice, StateFromReducersMapObject, PreloadedState } from "@reduxjs/toolkit";

const reducer = {
  one: oneSlice.reducer,
  two: twoSlice.reducer
};

export type RootState = StateFromReducersMapObject<typeof reducer>

export function initStore(preloadedState?: PreloadedState<RootState>) {
  return configureStore({
    reducer,
    preloadedState,
  });
}

type Store = ReturnType<typeof initStore>

export type AppDispatch = Store['dispatch']

Typescript Playground Link

like image 110
Linda Paiste Avatar answered Jan 18 '23 23:01

Linda Paiste