Skip to content

Commit c89d5e2

Browse files
authored
Add dictionary support to createEntityAdapter many methods (#444)
* Add dictionary support to addMany and upsertMany * Update type tests * Remove unplanned code * Do array check and conversion first for clarity * Add test for setAll on sorted adapter, use EntityId type
1 parent f14c0dc commit c89d5e2

File tree

6 files changed

+173
-19
lines changed

6 files changed

+173
-19
lines changed

src/entities/models.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,22 @@ export interface EntityStateAdapter<T> {
5757
action: PayloadAction<T>
5858
): S
5959

60-
addMany<S extends EntityState<T>>(state: PreventAny<S, T>, entities: T[]): S
6160
addMany<S extends EntityState<T>>(
6261
state: PreventAny<S, T>,
63-
entities: PayloadAction<T[]>
62+
entities: T[] | Record<EntityId, T>
63+
): S
64+
addMany<S extends EntityState<T>>(
65+
state: PreventAny<S, T>,
66+
entities: PayloadAction<T[] | Record<EntityId, T>>
6467
): S
6568

66-
setAll<S extends EntityState<T>>(state: PreventAny<S, T>, entities: T[]): S
6769
setAll<S extends EntityState<T>>(
6870
state: PreventAny<S, T>,
69-
entities: PayloadAction<T[]>
71+
entities: T[] | Record<EntityId, T>
72+
): S
73+
setAll<S extends EntityState<T>>(
74+
state: PreventAny<S, T>,
75+
entities: PayloadAction<T[] | Record<EntityId, T>>
7076
): S
7177

7278
removeOne<S extends EntityState<T>>(state: PreventAny<S, T>, key: EntityId): S
@@ -112,11 +118,11 @@ export interface EntityStateAdapter<T> {
112118

113119
upsertMany<S extends EntityState<T>>(
114120
state: PreventAny<S, T>,
115-
entities: T[]
121+
entities: T[] | Record<EntityId, T>
116122
): S
117123
upsertMany<S extends EntityState<T>>(
118124
state: PreventAny<S, T>,
119-
entities: PayloadAction<T[]>
125+
entities: PayloadAction<T[] | Record<EntityId, T>>
120126
): S
121127
}
122128

src/entities/sorted_state_adapter.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,24 @@ describe('Sorted State Adapter', () => {
8585
})
8686
})
8787

88+
it('should let you add many entities to the state from a dictionary', () => {
89+
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
90+
91+
const withManyMore = adapter.addMany(withOneEntity, {
92+
[AClockworkOrange.id]: AClockworkOrange,
93+
[AnimalFarm.id]: AnimalFarm
94+
})
95+
96+
expect(withManyMore).toEqual({
97+
ids: [AClockworkOrange.id, AnimalFarm.id, TheGreatGatsby.id],
98+
entities: {
99+
[TheGreatGatsby.id]: TheGreatGatsby,
100+
[AClockworkOrange.id]: AClockworkOrange,
101+
[AnimalFarm.id]: AnimalFarm
102+
}
103+
})
104+
})
105+
88106
it('should remove existing and add new ones on setAll', () => {
89107
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
90108

@@ -102,6 +120,23 @@ describe('Sorted State Adapter', () => {
102120
})
103121
})
104122

123+
it('should remove existing and add new ones on setAll when passing in a dictionary', () => {
124+
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
125+
126+
const withAll = adapter.setAll(withOneEntity, {
127+
[AClockworkOrange.id]: AClockworkOrange,
128+
[AnimalFarm.id]: AnimalFarm
129+
})
130+
131+
expect(withAll).toEqual({
132+
ids: [AClockworkOrange.id, AnimalFarm.id],
133+
entities: {
134+
[AClockworkOrange.id]: AClockworkOrange,
135+
[AnimalFarm.id]: AnimalFarm
136+
}
137+
})
138+
})
139+
105140
it('should remove existing and add new ones on addAll (deprecated)', () => {
106141
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
107142

@@ -380,4 +415,25 @@ describe('Sorted State Adapter', () => {
380415
}
381416
})
382417
})
418+
419+
it('should let you upsert many entities in the state when passing in a dictionary', () => {
420+
const firstChange = { title: 'Zack' }
421+
const withMany = adapter.setAll(state, [TheGreatGatsby])
422+
423+
const withUpserts = adapter.upsertMany(withMany, {
424+
[TheGreatGatsby.id]: { ...TheGreatGatsby, ...firstChange },
425+
[AClockworkOrange.id]: AClockworkOrange
426+
})
427+
428+
expect(withUpserts).toEqual({
429+
ids: [AClockworkOrange.id, TheGreatGatsby.id],
430+
entities: {
431+
[TheGreatGatsby.id]: {
432+
...TheGreatGatsby,
433+
...firstChange
434+
},
435+
[AClockworkOrange.id]: AClockworkOrange
436+
}
437+
})
438+
})
383439
})

src/entities/sorted_state_adapter.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {
33
IdSelector,
44
Comparer,
55
EntityStateAdapter,
6-
Update
6+
Update,
7+
EntityId
78
} from './models'
89
import { createStateOperator } from './state_adapter'
910
import { createUnsortedStateAdapter } from './unsorted_state_adapter'
@@ -23,7 +24,14 @@ export function createSortedStateAdapter<T>(
2324
return addManyMutably([entity], state)
2425
}
2526

26-
function addManyMutably(newModels: T[], state: R): void {
27+
function addManyMutably(
28+
newModels: T[] | Record<EntityId, T>,
29+
state: R
30+
): void {
31+
if (!Array.isArray(newModels)) {
32+
newModels = Object.values(newModels)
33+
}
34+
2735
const models = newModels.filter(
2836
model => !(selectIdValue(model, selectId) in state.entities)
2937
)
@@ -33,7 +41,10 @@ export function createSortedStateAdapter<T>(
3341
}
3442
}
3543

36-
function setAllMutably(models: T[], state: R): void {
44+
function setAllMutably(models: T[] | Record<EntityId, T>, state: R): void {
45+
if (!Array.isArray(models)) {
46+
models = Object.values(models)
47+
}
3748
state.entities = {}
3849
state.ids = []
3950

@@ -74,7 +85,14 @@ export function createSortedStateAdapter<T>(
7485
return upsertManyMutably([entity], state)
7586
}
7687

77-
function upsertManyMutably(entities: T[], state: R): void {
88+
function upsertManyMutably(
89+
entities: T[] | Record<EntityId, T>,
90+
state: R
91+
): void {
92+
if (!Array.isArray(entities)) {
93+
entities = Object.values(entities)
94+
}
95+
7896
const added: T[] = []
7997
const updated: Update<T>[] = []
8098

src/entities/unsorted_state_adapter.test.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,24 @@ describe('Unsorted State Adapter', () => {
6969
})
7070
})
7171

72+
it('should let you add many entities to the state from a dictionary', () => {
73+
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
74+
75+
const withManyMore = adapter.addMany(withOneEntity, {
76+
[AClockworkOrange.id]: AClockworkOrange,
77+
[AnimalFarm.id]: AnimalFarm
78+
})
79+
80+
expect(withManyMore).toEqual({
81+
ids: [TheGreatGatsby.id, AClockworkOrange.id, AnimalFarm.id],
82+
entities: {
83+
[TheGreatGatsby.id]: TheGreatGatsby,
84+
[AClockworkOrange.id]: AClockworkOrange,
85+
[AnimalFarm.id]: AnimalFarm
86+
}
87+
})
88+
})
89+
7290
it('should remove existing and add new ones on setAll', () => {
7391
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
7492

@@ -86,6 +104,23 @@ describe('Unsorted State Adapter', () => {
86104
})
87105
})
88106

107+
it('should remove existing and add new ones on setAll when passing in a dictionary', () => {
108+
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
109+
110+
const withAll = adapter.setAll(withOneEntity, {
111+
[AClockworkOrange.id]: AClockworkOrange,
112+
[AnimalFarm.id]: AnimalFarm
113+
})
114+
115+
expect(withAll).toEqual({
116+
ids: [AClockworkOrange.id, AnimalFarm.id],
117+
entities: {
118+
[AClockworkOrange.id]: AClockworkOrange,
119+
[AnimalFarm.id]: AnimalFarm
120+
}
121+
})
122+
})
123+
89124
it('should let you add remove an entity from the state', () => {
90125
const withOneEntity = adapter.addOne(state, TheGreatGatsby)
91126

@@ -230,14 +265,11 @@ describe('Unsorted State Adapter', () => {
230265

231266
/*
232267
Original code failed with a mish-mash of values, like:
233-
234268
{
235269
ids: [ 'c' ],
236270
entities: { b: { id: 'b', title: 'First' }, c: { id: 'c' } }
237271
}
238-
239272
We now expect that only 'c' will be left:
240-
241273
{
242274
ids: [ 'c' ],
243275
entities: { c: { id: 'c', title: 'First' } }
@@ -299,4 +331,25 @@ describe('Unsorted State Adapter', () => {
299331
}
300332
})
301333
})
334+
335+
it('should let you upsert many entities in the state when passing in a dictionary', () => {
336+
const firstChange = { title: 'Zack' }
337+
const withMany = adapter.setAll(state, [TheGreatGatsby])
338+
339+
const withUpserts = adapter.upsertMany(withMany, {
340+
[TheGreatGatsby.id]: { ...TheGreatGatsby, ...firstChange },
341+
[AClockworkOrange.id]: AClockworkOrange
342+
})
343+
344+
expect(withUpserts).toEqual({
345+
ids: [TheGreatGatsby.id, AClockworkOrange.id],
346+
entities: {
347+
[TheGreatGatsby.id]: {
348+
...TheGreatGatsby,
349+
...firstChange
350+
},
351+
[AClockworkOrange.id]: AClockworkOrange
352+
}
353+
})
354+
})
302355
})

src/entities/unsorted_state_adapter.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,21 @@ export function createUnsortedStateAdapter<T>(
2424
state.entities[key] = entity
2525
}
2626

27-
function addManyMutably(entities: T[], state: R): void {
27+
function addManyMutably(entities: T[] | Record<EntityId, T>, state: R): void {
28+
if (!Array.isArray(entities)) {
29+
entities = Object.values(entities)
30+
}
31+
2832
for (const entity of entities) {
2933
addOneMutably(entity, state)
3034
}
3135
}
3236

33-
function setAllMutably(entities: T[], state: R): void {
37+
function setAllMutably(entities: T[] | Record<EntityId, T>, state: R): void {
38+
if (!Array.isArray(entities)) {
39+
entities = Object.values(entities)
40+
}
41+
3442
state.ids = []
3543
state.entities = {}
3644

@@ -123,7 +131,14 @@ export function createUnsortedStateAdapter<T>(
123131
return upsertManyMutably([entity], state)
124132
}
125133

126-
function upsertManyMutably(entities: T[], state: R): void {
134+
function upsertManyMutably(
135+
entities: T[] | Record<EntityId, T>,
136+
state: R
137+
): void {
138+
if (!Array.isArray(entities)) {
139+
entities = Object.values(entities)
140+
}
141+
127142
const added: T[] = []
128143
const updated: Update<T>[] = []
129144

type-tests/files/createEntityAdapter.typetest.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,12 @@ function extractReducers<T>(
4141
})
4242

4343
expectType<ActionCreatorWithPayload<Entity>>(slice.actions.addOne)
44-
expectType<ActionCreatorWithPayload<Entity[]>>(slice.actions.addMany)
45-
expectType<ActionCreatorWithPayload<Entity[]>>(slice.actions.setAll)
44+
expectType<ActionCreatorWithPayload<Entity[] | Record<string, Entity>>>(
45+
slice.actions.addMany
46+
)
47+
expectType<ActionCreatorWithPayload<Entity[] | Record<string, Entity>>>(
48+
slice.actions.setAll
49+
)
4650
expectType<ActionCreatorWithPayload<EntityId>>(slice.actions.removeOne)
4751
expectType<ActionCreatorWithPayload<EntityId[]>>(slice.actions.removeMany)
4852
expectType<ActionCreatorWithoutPayload>(slice.actions.removeAll)
@@ -51,7 +55,9 @@ function extractReducers<T>(
5155
slice.actions.updateMany
5256
)
5357
expectType<ActionCreatorWithPayload<Entity>>(slice.actions.upsertOne)
54-
expectType<ActionCreatorWithPayload<Entity[]>>(slice.actions.upsertMany)
58+
expectType<ActionCreatorWithPayload<Entity[] | Record<string, Entity>>>(
59+
slice.actions.upsertMany
60+
)
5561
}
5662

5763
/**

0 commit comments

Comments
 (0)