Search code examples
typescriptreduxreselect

reselect createSelector type definitions for a redux store


I have the following state type for my redux reducer:

export interface CharactersState {
  characters: CharacterType[];
  error: string | null;
  isFetching: boolean;
}

I am then using this state type with combineReducer to create my store:

const initialState: CharactersState = {
  characters: [],
  error: null,
  isFetching: true // initiated with fetching true on app load
};

const characters = (state = initialState, action: CharacterActionsType) => {
  switch (action.type) {
   // commented out reducer logic here since it is not relevant
   //...

    default:
      return state;
  }
};

const rootReducer = combineReducers({
  characters
});

export type RootStateType = ReturnType<typeof rootReducer>;
export default rootReducer;

I am then using this rootReducer in a component controller:

const getCharacters = (state: RootStateType): CharactersState =>
  state.characters;

export const makeCharactersList = createSelector<
  RootStateType,
  CharactersState,
  CharacterListType[]
>(getCharacters, characters => {
  return characters.characters
    .map(character => {
      return {
        _id: character._id,
        name: character.name,
        description: character.description
      };
    })
    .sort((a, b) => {
      return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
    });
});

const mapStateToProps = (state: RootStateType) => {
  return {
    characters: makeCharactersList(state)
  };
};

const mapDispatchToProps = (dispatch: any) => {
  return {
    handleDeleteClick: (_id: string | null) => {
      dispatch(postDeleteCharacter(_id));
    }
  };
};

const connector = connect(mapStateToProps, mapDispatchToProps);
export type PropsFromRedux = ConnectedProps<typeof connector>;

export default connector(CharacterSidebar);

The controller is rendered in the parent component without passing any props:

<Sidebar />

How do I write the correct type definition for makeCharactersList? It seems that createSelector should take the types of State, OwnProps, ConnectedProps - in my case it seems that OwnProps would just be an empty object?

But when I set the type definition to:

createSelector<
  RootStateType,
 {},
  CharacterListType[]

I get the following error:

Property 'characters' does not exist on type '{}'.


Solution

  • There are multiple overloads for createSelector, but the one that you are matching is this:

    export function createSelector<S, R1, T>(
      selector: (state: S) => R,
      combiner: (res: R1) => T,
    ): OutputSelector<S, T, (res: R1) => T>;
    

    The three generics are:

    1. S - the type of the root state - RootStateType
    2. R1 - the value selected by the first selector, which is also the input for the combiner - CharactersState
    3. T - the value that you return from the combiner - Pick<CharacterType, '_id' | 'name' | 'description'>[] (an array of characters with only the selected properties)

    Probably your CharacterListType is an alias for the picked properties in #3? If so, then you had it correct in your code block.

    export const makeCharactersList = createSelector<
      RootStateType,
      CharactersState,
      CharacterListType[]
    >(getCharacters, characters => {
    ...