diff --git a/docs/api/createEntityAdapter.md b/docs/api/createEntityAdapter.md index 373b8ef28c..e13e7de8df 100644 --- a/docs/api/createEntityAdapter.md +++ b/docs/api/createEntityAdapter.md @@ -176,6 +176,7 @@ export interface EntitySelectors { selectEntities: (state: V) => Dictionary selectAll: (state: V) => T[] selectTotal: (state: V) => number + selectById: (state: V, id: EntityId) => T | undefined } export interface EntityAdapter extends EntityStateAdapter { @@ -247,12 +248,13 @@ const booksSlice = createSlice({ ### Selector Functions -The entity adapter will contain a `getSelectors()` function that returns a set of four selectors that know how to read the contents of an entity state object: +The entity adapter will contain a `getSelectors()` function that returns a set of selectors that know how to read the contents of an entity state object: - `selectIds`: returns the `state.ids` array - `selectEntities`: returns the `state.entities` lookup table - `selectAll`: maps over the `state.ids` array, and returns an array of entities in the same order - `selectTotal`: returns the total number of entities being stored in this state +- `selectById`: given the state and an entity ID, returns the entity with that ID or `undefined` Each selector function will be created using the `createSelector` function from Reselect, to enable memoizing calculation of the results. diff --git a/etc/redux-toolkit.api.md b/etc/redux-toolkit.api.md index 870e272cbe..c32727329d 100644 --- a/etc/redux-toolkit.api.md +++ b/etc/redux-toolkit.api.md @@ -13,8 +13,12 @@ import { DeepPartial } from 'redux'; import { Dispatch } from 'redux'; import { Draft } from 'immer'; import { Middleware } from 'redux'; +import { OutputParametricSelector } from 'reselect'; +import { OutputSelector } from 'reselect'; +import { ParametricSelector } from 'reselect'; import { Reducer } from 'redux'; import { ReducersMapObject } from 'redux'; +import { Selector } from 'reselect'; import { Store } from 'redux'; import { StoreEnhancer } from 'redux'; import { ThunkAction } from 'redux-thunk'; @@ -212,6 +216,12 @@ export type IdSelector = (model: T) => EntityId; // @public export function isPlain(val: any): boolean; +export { OutputParametricSelector } + +export { OutputSelector } + +export { ParametricSelector } + // @public export type PayloadAction

= { payload: P; @@ -240,6 +250,8 @@ export type PrepareAction

= ((...args: any[]) => { error: any; }); +export { Selector } + // @public export interface SerializableStateInvariantMiddlewareOptions { getEntries?: (value: any) => [string, any][]; diff --git a/src/entities/models.ts b/src/entities/models.ts index 30996a57cb..4a5696178c 100644 --- a/src/entities/models.ts +++ b/src/entities/models.ts @@ -125,6 +125,7 @@ export interface EntitySelectors { selectEntities: (state: V) => Dictionary selectAll: (state: V) => T[] selectTotal: (state: V) => number + selectById: (state: V, id: EntityId) => T | undefined } /** diff --git a/src/entities/state_selectors.test.ts b/src/entities/state_selectors.test.ts index d07303838e..7b8c11b735 100644 --- a/src/entities/state_selectors.test.ts +++ b/src/entities/state_selectors.test.ts @@ -57,6 +57,13 @@ describe('Entity State Selectors', () => { expect(total).toEqual(3) }) + + it('should create a selector for selecting a single item by ID', () => { + const first = selectors.selectById(state, AClockworkOrange.id) + expect(first).toBe(AClockworkOrange) + const second = selectors.selectById(state, AnimalFarm.id) + expect(second).toBe(AnimalFarm) + }) }) describe('Uncomposed Selectors', () => { @@ -110,5 +117,12 @@ describe('Entity State Selectors', () => { expect(total).toEqual(3) }) + + it('should create a selector for selecting a single item by ID', () => { + const first = selectors.selectById(state, AClockworkOrange.id) + expect(first).toBe(AClockworkOrange) + const second = selectors.selectById(state, AnimalFarm.id) + expect(second).toBe(AnimalFarm) + }) }) }) diff --git a/src/entities/state_selectors.ts b/src/entities/state_selectors.ts index 636792c1da..d7370c879a 100644 --- a/src/entities/state_selectors.ts +++ b/src/entities/state_selectors.ts @@ -1,5 +1,5 @@ import { createSelector } from 'reselect' -import { EntityState, EntitySelectors, Dictionary } from './models' +import { EntityState, EntitySelectors, Dictionary, EntityId } from './models' export function createSelectorsFactory() { function getSelectors(): EntitySelectors> @@ -10,7 +10,9 @@ export function createSelectorsFactory() { selectState?: (state: any) => EntityState ): EntitySelectors { const selectIds = (state: any) => state.ids + const selectEntities = (state: EntityState) => state.entities + const selectAll = createSelector( selectIds, selectEntities, @@ -18,6 +20,10 @@ export function createSelectorsFactory() { ids.map((id: any) => (entities as any)[id]) ) + const selectId = (_: any, id: EntityId) => id + + const selectById = (entities: Dictionary, id: EntityId) => entities[id] + const selectTotal = createSelector(selectIds, ids => ids.length) if (!selectState) { @@ -25,15 +31,19 @@ export function createSelectorsFactory() { selectIds, selectEntities, selectAll, - selectTotal + selectTotal, + selectById: createSelector(selectEntities, selectId, selectById) } } + const selectGlobalizedEntities = createSelector(selectState, selectEntities) + return { selectIds: createSelector(selectState, selectIds), - selectEntities: createSelector(selectState, selectEntities), + selectEntities: selectGlobalizedEntities, selectAll: createSelector(selectState, selectAll), - selectTotal: createSelector(selectState, selectTotal) + selectTotal: createSelector(selectState, selectTotal), + selectById: createSelector(selectGlobalizedEntities, selectId, selectById) } } diff --git a/src/index.ts b/src/index.ts index c61b909506..670d542c10 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,13 @@ import { enableES5 } from 'immer' export * from 'redux' export { default as createNextState, Draft } from 'immer' -export { createSelector } from 'reselect' +export { + createSelector, + Selector, + OutputParametricSelector, + OutputSelector, + ParametricSelector +} from 'reselect' export { ThunkAction } from 'redux-thunk' // We deliberately enable Immer's ES5 support, on the grounds that