redux-toolkit: Using createSelector with EntityAdapter's getSelectors causes '$CombinedState' error

Problem

When using the EntityAdapter’s exported selectors with reselects createSelector a Typescript error is thrown regarding the $CombinedState symbol not being exported.

Example:

import { createSelector } from '@reduxjs/toolkit'

import { sessionsAdapter } from '../reducers/sessions'
import { RootState } from '../types'

export const { selectAll: selectAllSessions } = sessionsAdapter.getSelectors((state: RootState) => state.sessions)
export const currentSessionSelector = createSelector(selectAllSessions, (sessions) => sessions[0])

Results in the following error

Exported variable 'currentSessionSelector' has or is using name '$CombinedState' from external module "../node_modules/redux/index" but cannot be named.ts(4023)

Package Versions

Current:

This code was previously working with the following package versions:

Previous:

I’m not sure at which point it broke as I upgraded to the latest and I’m now working backwards.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 6
  • Comments: 40 (7 by maintainers)

Most upvoted comments

We’ve encountered the same $CombinedState issue.

Here is what we did to fix that:

1. Create reducers map object

const reducers = {
  foo: fooSlice.reducer,
  bar: barSlice.reducer,
  // Register all your reducers as usual.
};

2. Define a correctly typed root reducer

type ReducersMapObject = typeof reducers;

export const rootReducer: Reducer<
  StateFromReducersMapObject<ReducersMapObject>,
  ActionFromReducersMapObject<ReducersMapObject>
> = combineReducers(reducers);

Unlike combineReducers return type from Redux, we don’t wrap ReducersMapObject with CombineState for the state part.

3. Create and export root state as usual

export type RootState = ReturnType<typeof rootReducer>;

Finally, we’ll have a state that does not include $CombinedState anywhere.

CleanState generic type solution is elegant but brings some issues:

  • It uses recursion, which transforms original state passed in parameter to a big object type literal.
  • Then, when emitting types, generated files have an enormous size and we encounter heap out of memory issues.

For anyone reading this before a real solution is presented, I’ve taken the CleanState type from here:

import type { CombinedState } from '@reduxjs/toolkit';

type CleanState<T> = T extends CombinedState<infer S> ? { [K in keyof S]: CleanState<S[K]> } : T;

and dropped it in my project so I can wrap the RootState in it:

export type RootState = CleanState<ReturnType<typeof rootReducer>>;

With this, the project builds fine and the definitions for emitted types for the selectors and state are as expected.

I’ve encountered the same issue and solved it with @thibaultboursier solution but with a small change

const reducers = {
  foo: fooSlice.reducer,
  bar: barSlice.reducer,
};

// No need to explicitly set the type here
export const rootReducer = combineReducers(reducers);

export type RootState = StateFromReducersMapObject<typeof reducers>

And in case you need to get the dispatch type, here is what I’ve used:

type AppActions = ActionFromReducersMapObject<typeof reducers>;
type AppDispatch = ExtractDispatchExtensions<[ThunkMiddlewareFor<RootState>]> & Dispatch<AppActions>;

Has there been any progress on this issue, or is there a workaround in the mean time?

This should be fixed as of https://github.com/reduxjs/redux-toolkit/releases/tag/v2.0.0-alpha.5 , since we’ve dropped $CombinedState from the redux core.

I’m intested in this, too! We’re currently using the workaround where we use npm-force-resolutions to use dependency reselect version 4.1.0 instead of 4.1.5.

Yeah. What we really need is a complete project that actually reproduces the problem and shows this error happening.

Without that, there really isn’t anything we can do.