Search code examples
typescriptreduxredux-toolkit

How to infer a complete map of all the sub-stores, their actions, and their payloads?


TL;DR: In a composite store, is there a way to infer all actions with their payloads grouped by the sub-store they are related to in a single type entity (e.g., interface)?


I have set up a Redux store with a couple of sub-stores, the total picture is similar to this:

  • there is an action setAppLoading(isLoading :boolean) that sets state.app.loading to the action.payload;
  • there is an action setUserName(name: string | null) that also sets state.user.name to the action.payload (the name is nullable, — e.g., if user does not exist);
  • there is an action logOut() that does some API stuff and then calls setUserName(null);

What I want to achieve in the end is to infer the complete structure of the store, as follows:

interface ActionsMap {
  app: {
    setAppLoading(isLoading: boolean): void
  }
  user: {
    setUserName(name: string | null): void
    logOut(): void
  }
}

Is it possible to infer this type from already existing entities (see below)? I want to define things once, and I don't want to re-export things, because I will definitely have situations when I have created an action but forgot to refer to it in other places.


I already have a store instance (created with configureStore() and combineReducers()) that is properly typed, and I can already infer all sub-store names from it:

type State = ReturnType<typeof store.getState>

// omitting the internal key `typeof $CombinedState`
type SubStoreName = Extract<keyof State, string>

For each store separately there is also already a collection of all related actions, that is also properly typed:

import * as appActions from 'store/app/actions'
import * as userActions from 'store/user/actions'

dispatch(appActions.setAppLoading(true))
dispatch(userActions.logOut())

So far, the closest thing that I have came up with is this:

const actionsMap = {
  app: appActions,
  game: gameActions,
} satisfies Record<SubStoreName, unknown>

type ActionsMap = typeof actionsMap

This requires me to "re-export things", which I don't want, but it also will notify me if I forget to re-export something.


Solution

  • No, and it's probably also not the best way of thinking.
    While in slices, there is often an action "owned" by that slice, that action can still be listened to by all the reducers in your application.

    It's much better to think in ways of "this event happened in my application" instead of "this action was sent to that reducer".

    Think in events, not setters. See the Redux Style Guide on the topic.