1

I am trying to infer the type of RootState but I get this error, when trying to use it in a selector:

const tagsSelector = (state: RootState) => state.tags;
TS2339: Property 'tags' does not exist on type 'CombinedState<{ tags: CombinedState<{ tagsSet: TagsState; }>; } | { queue: CombinedState<{ clientCard: ClientCardState; clientCardTasks: ClientCardTasksState; }>; }>'.   Property 'tags' does not exist on type '{ readonly [$CombinedState]?: undefined; } & { queue: CombinedState<{ clientCard: ClientCardState; clientCardTasks: ClientCardTasksState; }>; }'.

RootState type I infer that

const typedReducers = typedModules.map(m => m.getTypedReducers() ).reduce((accum, r) => {
    return {...accum, ...r}
});

const rootTypedReducers = combineReducers(typedReducers);

export type RootState = ReturnType<typeof rootTypedReducers>;

getTypedReducers() just return root reducer of each module

getTypedReducers() {
        return {tags: combineReducers({
                    tagsSet: tagsReducers,
            })};
    }

However, if I only use one module, then everything works.

ilya
  • 13
  • 4

1 Answers1

0

This is how typescript has interpreted your state type:

CombinedState<{ tags: CombinedState<{ tagsSet: TagsState; }>; } | { queue: CombinedState<{ clientCard: ClientCardState; clientCardTasks: ClientCardTasksState; }>; }>

The problem is that it's an OR rather than an AND. Your state is seen to either have keys 'tags' or 'queue' | 'clientCard' | 'clientCardTasks'. So as far as typescript is concerned, tags isn't guaranteed to always be present.

This bad type is created in your typedReducures because you are using array functions like map and reduce. Typescript is not able to understand that return {...accum, ...r} must include all of the elements of the individual getTypedReducers() maps. So we need to override there return type there.

There is a classic UnionToIntersection utility type created by @jcalz that we can make use of here.

type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type Reducers = UnionToIntersection<ReturnType<typeof typedModules[number]['getTypedReducers']>>

const typedReducers = typedModules.map(m => m.getTypedReducers()).reduce((accum, r) => {
  return { ...accum, ...r }
}) as Reducers;

Now there is no more error in the selector because all of the keys are known to definitely exist.

Typescript Playground Link

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102