Search code examples
typescriptredux

How to infer the type of an object of reducer-functions, so that the type will include the keys and the function results?


I have a object, that maps reducer names with their reducer functions:

const reducers = {
  clients: ClientsReducer,
  mails:   MailsReducer,
  users:   UsersReducer,
  // ...
}

The reducers type is:

interface Reducers {
  clients: (state: ClientsState, action: any) => ClientsState,
  mails:   (state: MailsState, action: any) => MailsState,
  users:   (state: UsersState, action: any) => UsersState,
  // ...
}

For my application, I need a slightly different type to select the state with useSelector.

The required type is:

interface RootState {
  clients: ClientsState;
  mails:   MailsState;
  users:   UsersState;
  // ...
}

// ...

const clients = useSelector((state: RootState) => state.clients);

Redux suggests to infer the type from the combineReducer result by doing:

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

const rootState = combineReducers({
  ...reducers
});

type RootState = ReturnType<typeof rootState>;

This is not applicable to my situation, as my Store is created dynamically from different "modules". At this point, I have no access to the result of combineReducers.

The best I could do was:

type StateType<T extends Record<S, (...args: any) => any>, S extends string> = T extends Record<S, (...args: any) => infer R> ? Record<S, R> : any;

type RootState = StateType<typeof reducers, keyof typeof reducers>;

/*
 * RootState = {
 *    clients: ClientsState | MailsState | UsersState;
 *    mails:   ClientsState | MailsState | UsersState;
 *    users:   ClientsState | MailsState | UsersState;
 * }

This is not quite right, as for each key there is a union of all states. I see that "S" is causing this, but I don't know how to eliminate/fix it.

Is there any way to infer the RootState from the reducers object, so that the type will include the keys (reducer names) and the function results (reducer states)?


Solution

  • You can use a mapped type to easily get each of the reducers' return types:

    type RootState = { [K in keyof Reducers]: ReturnType<Reducers[K]> };
    

    Playground