Skip to content

Commit 3025d77

Browse files
authored
Feature/entity selectors (#440)
* Add a `selectById` selector * Export Reselect types * Update API
1 parent f64f750 commit 3025d77

File tree

6 files changed

+51
-6
lines changed

6 files changed

+51
-6
lines changed

docs/api/createEntityAdapter.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ export interface EntitySelectors<T, V> {
176176
selectEntities: (state: V) => Dictionary<T>
177177
selectAll: (state: V) => T[]
178178
selectTotal: (state: V) => number
179+
selectById: (state: V, id: EntityId) => T | undefined
179180
}
180181

181182
export interface EntityAdapter<T> extends EntityStateAdapter<T> {
@@ -247,12 +248,13 @@ const booksSlice = createSlice({
247248

248249
### Selector Functions
249250

250-
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:
251+
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:
251252

252253
- `selectIds`: returns the `state.ids` array
253254
- `selectEntities`: returns the `state.entities` lookup table
254255
- `selectAll`: maps over the `state.ids` array, and returns an array of entities in the same order
255256
- `selectTotal`: returns the total number of entities being stored in this state
257+
- `selectById`: given the state and an entity ID, returns the entity with that ID or `undefined`
256258

257259
Each selector function will be created using the `createSelector` function from Reselect, to enable memoizing calculation of the results.
258260

etc/redux-toolkit.api.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ import { DeepPartial } from 'redux';
1313
import { Dispatch } from 'redux';
1414
import { Draft } from 'immer';
1515
import { Middleware } from 'redux';
16+
import { OutputParametricSelector } from 'reselect';
17+
import { OutputSelector } from 'reselect';
18+
import { ParametricSelector } from 'reselect';
1619
import { Reducer } from 'redux';
1720
import { ReducersMapObject } from 'redux';
21+
import { Selector } from 'reselect';
1822
import { Store } from 'redux';
1923
import { StoreEnhancer } from 'redux';
2024
import { ThunkAction } from 'redux-thunk';
@@ -212,6 +216,12 @@ export type IdSelector<T> = (model: T) => EntityId;
212216
// @public
213217
export function isPlain(val: any): boolean;
214218

219+
export { OutputParametricSelector }
220+
221+
export { OutputSelector }
222+
223+
export { ParametricSelector }
224+
215225
// @public
216226
export type PayloadAction<P = void, T extends string = string, M = never, E = never> = {
217227
payload: P;
@@ -240,6 +250,8 @@ export type PrepareAction<P> = ((...args: any[]) => {
240250
error: any;
241251
});
242252

253+
export { Selector }
254+
243255
// @public
244256
export interface SerializableStateInvariantMiddlewareOptions {
245257
getEntries?: (value: any) => [string, any][];

src/entities/models.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export interface EntitySelectors<T, V> {
125125
selectEntities: (state: V) => Dictionary<T>
126126
selectAll: (state: V) => T[]
127127
selectTotal: (state: V) => number
128+
selectById: (state: V, id: EntityId) => T | undefined
128129
}
129130

130131
/**

src/entities/state_selectors.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ describe('Entity State Selectors', () => {
5757

5858
expect(total).toEqual(3)
5959
})
60+
61+
it('should create a selector for selecting a single item by ID', () => {
62+
const first = selectors.selectById(state, AClockworkOrange.id)
63+
expect(first).toBe(AClockworkOrange)
64+
const second = selectors.selectById(state, AnimalFarm.id)
65+
expect(second).toBe(AnimalFarm)
66+
})
6067
})
6168

6269
describe('Uncomposed Selectors', () => {
@@ -110,5 +117,12 @@ describe('Entity State Selectors', () => {
110117

111118
expect(total).toEqual(3)
112119
})
120+
121+
it('should create a selector for selecting a single item by ID', () => {
122+
const first = selectors.selectById(state, AClockworkOrange.id)
123+
expect(first).toBe(AClockworkOrange)
124+
const second = selectors.selectById(state, AnimalFarm.id)
125+
expect(second).toBe(AnimalFarm)
126+
})
113127
})
114128
})

src/entities/state_selectors.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createSelector } from 'reselect'
2-
import { EntityState, EntitySelectors, Dictionary } from './models'
2+
import { EntityState, EntitySelectors, Dictionary, EntityId } from './models'
33

44
export function createSelectorsFactory<T>() {
55
function getSelectors(): EntitySelectors<T, EntityState<T>>
@@ -10,30 +10,40 @@ export function createSelectorsFactory<T>() {
1010
selectState?: (state: any) => EntityState<T>
1111
): EntitySelectors<T, any> {
1212
const selectIds = (state: any) => state.ids
13+
1314
const selectEntities = (state: EntityState<T>) => state.entities
15+
1416
const selectAll = createSelector(
1517
selectIds,
1618
selectEntities,
1719
(ids: T[], entities: Dictionary<T>): any =>
1820
ids.map((id: any) => (entities as any)[id])
1921
)
2022

23+
const selectId = (_: any, id: EntityId) => id
24+
25+
const selectById = (entities: Dictionary<T>, id: EntityId) => entities[id]
26+
2127
const selectTotal = createSelector(selectIds, ids => ids.length)
2228

2329
if (!selectState) {
2430
return {
2531
selectIds,
2632
selectEntities,
2733
selectAll,
28-
selectTotal
34+
selectTotal,
35+
selectById: createSelector(selectEntities, selectId, selectById)
2936
}
3037
}
3138

39+
const selectGlobalizedEntities = createSelector(selectState, selectEntities)
40+
3241
return {
3342
selectIds: createSelector(selectState, selectIds),
34-
selectEntities: createSelector(selectState, selectEntities),
43+
selectEntities: selectGlobalizedEntities,
3544
selectAll: createSelector(selectState, selectAll),
36-
selectTotal: createSelector(selectState, selectTotal)
45+
selectTotal: createSelector(selectState, selectTotal),
46+
selectById: createSelector(selectGlobalizedEntities, selectId, selectById)
3747
}
3848
}
3949

src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { enableES5 } from 'immer'
22
export * from 'redux'
33
export { default as createNextState, Draft } from 'immer'
4-
export { createSelector } from 'reselect'
4+
export {
5+
createSelector,
6+
Selector,
7+
OutputParametricSelector,
8+
OutputSelector,
9+
ParametricSelector
10+
} from 'reselect'
511
export { ThunkAction } from 'redux-thunk'
612

713
// We deliberately enable Immer's ES5 support, on the grounds that

0 commit comments

Comments
 (0)