Search code examples
typescripttypesreact-reduxredux-toolkitcreateentityadapter

How to adapt to Redux Toolkit's Updated selectById Type in EntitySelectors


I've recently updated "react-redux": "8.0.4" to "8.0.5" and "@reduxjs/toolkit" from "1.8.6" to "2.0.0". In doing so I've noticed a change in the type definition of EntitySelectors in Redux Toolkit's EntityAdapter, particularly for the selectById selector.

Previously, the type explicitly allowed for undefined as a possible return type. However, the updated definition uses Compute<UncheckedIndexedAccess<T>>, which seems to complicate type inference.

This is the old definition:

export interface EntitySelectors<T, V> {
  selectIds: (state: V) => EntityId[];
  selectEntities: (state: V) => Record<EntityId, T>;
  selectAll: (state: V) => T[];
  selectTotal: (state: V) => number;
  selectById: (state: V, id: EntityId) => T | undefined;
}

And this is the new definition:

export interface EntitySelectors<T, V, Id extends EntityId> {
    selectIds: (state: V) => Id[];
    selectEntities: (state: V) => Record<Id, T>;
    selectAll: (state: V) => T[];
    selectTotal: (state: V) => number;
    selectById: (state: V, id: Id) => Compute<UncheckedIndexedAccess<T>>;
}

Previously I would define my entity selectors like so:

/* Export selectors ------------------------------------- */
export const selectUsers = usersAdapter.getSelectors(
    (state: RootState) => state.users
);

And then call them like so:

const user = useAppSelector((state) =>
    selectUsers.selectById(state, userId ?? "")
);

user would then be inferred as User_Entity or undefined

However since updating user is now only inferred as User_Entity which then doesn't cover the posibilty of undefined values, say for example if the userId is incorrect and doesn't exist as an id in the users slice.

I've got around this by doing the following:

export const { selectById, ...rest } = usersAdapter.getSelectors(
    (state: RootState) => state.users,
);

export const selectUsers = {
    ...rest,
    selectById: (state: RootState, userId: EntityId): User_Entity | undefined =>
        selectById(state, userId),
};

However I'm curious if there are better ways to approach this, especially in light of the new type definitions. Any insights or suggestions would be greatly appreciated!


Solution

  • As noted in the migration guide, the entity typing now depends on your noUncheckedIndexedAccess setting - if it's on, it'll be possibly undefined, if not it won't.

    As you've noted, you can manually annotate to add the possibly undefined type in a case by case basis.