diff --git a/docs/api/createEntityAdapter.md b/docs/api/createEntityAdapter.md index d3ffda2e07..373b8ef28c 100644 --- a/docs/api/createEntityAdapter.md +++ b/docs/api/createEntityAdapter.md @@ -118,8 +118,6 @@ export interface Dictionary extends DictionaryNum { export type Update = { id: EntityId; changes: Partial } -export type EntityMap = (entity: T) => T - export interface EntityState { ids: EntityId[] entities: Dictionary @@ -171,9 +169,6 @@ export interface EntityStateAdapter { state: S, entities: PayloadAction ): S - - map>(state: S, map: EntityMap): S - map>(state: S, map: PayloadAction>): S } export interface EntitySelectors { @@ -208,7 +203,6 @@ The primary content of an entity adapter is a set of generated reducer functions - `updateMany`: accepts an array of update objects, and updates all corresponding entities - `upsertOne`: accepts a single entity. If an entity with that ID exists, the fields in the update will be merged into the existing entity, with any matching fields overwriting the existing values. If the entity does not exist, it will be added. - `upsertMany`: accepts an array of entities that will be upserted. -- `map`: accepts a callback function that will be run against each existing entity, and may return a change description object. Afterwards, all changes will be merged into the corresponding existing entities. Each method has a signature that looks like: diff --git a/etc/redux-toolkit.api.md b/etc/redux-toolkit.api.md index d8e0eedef7..870e272cbe 100644 --- a/etc/redux-toolkit.api.md +++ b/etc/redux-toolkit.api.md @@ -185,9 +185,6 @@ export interface EntityAdapter extends EntityStateAdapter { sortComparer: false | Comparer; } -// @alpha (undocumented) -export type EntityMap = (entity: T) => T; - // @alpha (undocumented) export interface EntityState { // (undocumented) diff --git a/src/entities/index.ts b/src/entities/index.ts index eded42c904..e6fe1d87db 100644 --- a/src/entities/index.ts +++ b/src/entities/index.ts @@ -4,7 +4,6 @@ export { EntityState, EntityAdapter, Update, - EntityMap, IdSelector, Comparer } from './models' diff --git a/src/entities/models.ts b/src/entities/models.ts index 9f7b217d4f..30996a57cb 100644 --- a/src/entities/models.ts +++ b/src/entities/models.ts @@ -1,4 +1,5 @@ import { PayloadAction } from '../createAction' +import { IsAny } from '../tsHelpers' /** * @alpha @@ -34,11 +35,6 @@ export interface Dictionary extends DictionaryNum { */ export type Update = { id: EntityId; changes: Partial } -/** - * @alpha - */ -export type EntityMap = (entity: T) => T - /** * @alpha */ @@ -52,50 +48,76 @@ export interface EntityDefinition { sortComparer: false | Comparer } +export type PreventAny = IsAny, S> + export interface EntityStateAdapter { - addOne>(state: S, entity: T): S - addOne>(state: S, action: PayloadAction): S + addOne>(state: PreventAny, entity: T): S + addOne>( + state: PreventAny, + action: PayloadAction + ): S - addMany>(state: S, entities: T[]): S - addMany>(state: S, entities: PayloadAction): S + addMany>(state: PreventAny, entities: T[]): S + addMany>( + state: PreventAny, + entities: PayloadAction + ): S - setAll>(state: S, entities: T[]): S - setAll>(state: S, entities: PayloadAction): S + setAll>(state: PreventAny, entities: T[]): S + setAll>( + state: PreventAny, + entities: PayloadAction + ): S - removeOne>(state: S, key: EntityId): S - removeOne>(state: S, key: PayloadAction): S + removeOne>(state: PreventAny, key: EntityId): S + removeOne>( + state: PreventAny, + key: PayloadAction + ): S - removeMany>(state: S, keys: EntityId[]): S removeMany>( - state: S, + state: PreventAny, + keys: EntityId[] + ): S + removeMany>( + state: PreventAny, keys: PayloadAction ): S - removeAll>(state: S): S + removeAll>(state: PreventAny): S - updateOne>(state: S, update: Update): S updateOne>( - state: S, + state: PreventAny, + update: Update + ): S + updateOne>( + state: PreventAny, update: PayloadAction> ): S - updateMany>(state: S, updates: Update[]): S updateMany>( - state: S, + state: PreventAny, + updates: Update[] + ): S + updateMany>( + state: PreventAny, updates: PayloadAction[]> ): S - upsertOne>(state: S, entity: T): S - upsertOne>(state: S, entity: PayloadAction): S + upsertOne>(state: PreventAny, entity: T): S + upsertOne>( + state: PreventAny, + entity: PayloadAction + ): S - upsertMany>(state: S, entities: T[]): S upsertMany>( - state: S, + state: PreventAny, + entities: T[] + ): S + upsertMany>( + state: PreventAny, entities: PayloadAction ): S - - map>(state: S, map: EntityMap): S - map>(state: S, map: PayloadAction>): S } export interface EntitySelectors { diff --git a/src/entities/sorted_state_adapter.test.ts b/src/entities/sorted_state_adapter.test.ts index 13419d78e5..8b45a78868 100644 --- a/src/entities/sorted_state_adapter.test.ts +++ b/src/entities/sorted_state_adapter.test.ts @@ -331,40 +331,6 @@ describe('Sorted State Adapter', () => { }) }) - it('should let you map over entities in the state', () => { - const firstChange = { ...TheGreatGatsby, title: 'First change' } - const secondChange = { ...AClockworkOrange, title: 'Second change' } - - const withMany = adapter.setAll(state, [ - TheGreatGatsby, - AClockworkOrange, - AnimalFarm - ]) - - const withUpdates = adapter.map(withMany, book => - book.title === TheGreatGatsby.title - ? firstChange - : book.title === AClockworkOrange.title - ? secondChange - : book - ) - - expect(withUpdates).toEqual({ - ids: [AnimalFarm.id, TheGreatGatsby.id, AClockworkOrange.id], - entities: { - [AnimalFarm.id]: AnimalFarm, - [TheGreatGatsby.id]: { - ...TheGreatGatsby, - ...firstChange - }, - [AClockworkOrange.id]: { - ...AClockworkOrange, - ...secondChange - } - } - }) - }) - it('should let you add one entity to the state with upsert()', () => { const withOneEntity = adapter.upsertOne(state, TheGreatGatsby) expect(withOneEntity).toEqual({ diff --git a/src/entities/sorted_state_adapter.ts b/src/entities/sorted_state_adapter.ts index 79e6e03f3b..93c6e5e7a4 100644 --- a/src/entities/sorted_state_adapter.ts +++ b/src/entities/sorted_state_adapter.ts @@ -3,9 +3,7 @@ import { IdSelector, Comparer, EntityStateAdapter, - Update, - EntityMap, - EntityId + Update } from './models' import { createStateOperator } from './state_adapter' import { createUnsortedStateAdapter } from './unsorted_state_adapter' @@ -72,21 +70,6 @@ export function createSortedStateAdapter( } } - function mapMutably(updatesOrMap: EntityMap, state: R): void { - const updates: Update[] = state.ids.reduce( - (changes: Update[], id: EntityId) => { - const change = updatesOrMap(state.entities[id]!) - if (change !== state.entities[id]) { - changes.push({ id, changes: change }) - } - return changes - }, - [] - ) - - updateManyMutably(updates, state) - } - function upsertOneMutably(entity: T, state: R): void { return upsertManyMutably([entity], state) } @@ -151,7 +134,6 @@ export function createSortedStateAdapter( setAll: createStateOperator(setAllMutably), addMany: createStateOperator(addManyMutably), updateMany: createStateOperator(updateManyMutably), - upsertMany: createStateOperator(upsertManyMutably), - map: createStateOperator(mapMutably) + upsertMany: createStateOperator(upsertManyMutably) } } diff --git a/src/entities/unsorted_state_adapter.test.ts b/src/entities/unsorted_state_adapter.test.ts index 5b83f1455e..c444833738 100644 --- a/src/entities/unsorted_state_adapter.test.ts +++ b/src/entities/unsorted_state_adapter.test.ts @@ -250,40 +250,6 @@ describe('Unsorted State Adapter', () => { expect(entities.c).toBeTruthy() }) - it('should let you map over entities in the state', () => { - const firstChange = { ...TheGreatGatsby, title: 'First change' } - const secondChange = { ...AClockworkOrange, title: 'Second change' } - - const withMany = adapter.setAll(state, [ - TheGreatGatsby, - AClockworkOrange, - AnimalFarm - ]) - - const withUpdates = adapter.map(withMany, book => - book.title === TheGreatGatsby.title - ? firstChange - : book.title === AClockworkOrange.title - ? secondChange - : book - ) - - expect(withUpdates).toEqual({ - ids: [TheGreatGatsby.id, AClockworkOrange.id, AnimalFarm.id], - entities: { - [TheGreatGatsby.id]: { - ...TheGreatGatsby, - ...firstChange - }, - [AClockworkOrange.id]: { - ...AClockworkOrange, - ...secondChange - }, - [AnimalFarm.id]: AnimalFarm - } - }) - }) - it('should let you add one entity to the state with upsert()', () => { const withOneEntity = adapter.upsertOne(state, TheGreatGatsby) expect(withOneEntity).toEqual({ diff --git a/src/entities/unsorted_state_adapter.ts b/src/entities/unsorted_state_adapter.ts index 5791abead7..7386d9a820 100644 --- a/src/entities/unsorted_state_adapter.ts +++ b/src/entities/unsorted_state_adapter.ts @@ -3,7 +3,6 @@ import { EntityStateAdapter, IdSelector, Update, - EntityMap, EntityId } from './models' import { createStateOperator } from './state_adapter' @@ -57,7 +56,7 @@ export function createUnsortedStateAdapter( } } - function removeAll(state: S): S { + function removeAll(state: R): any { return Object.assign({}, state, { ids: [], entities: {} @@ -120,22 +119,6 @@ export function createUnsortedStateAdapter( } } - function mapMutably(map: EntityMap, state: R): void { - const changes: Update[] = state.ids.reduce( - (changes: Update[], id: EntityId) => { - const change = map(state.entities[id]!) - if (change !== state.entities[id]) { - changes.push({ id, changes: change }) - } - return changes - }, - [] - ) - const updates = changes.filter(({ id }) => id in state.entities) - - return updateManyMutably(updates, state) - } - function upsertOneMutably(entity: T, state: R): void { return upsertManyMutably([entity], state) } @@ -167,7 +150,6 @@ export function createUnsortedStateAdapter( upsertOne: createStateOperator(upsertOneMutably), upsertMany: createStateOperator(upsertManyMutably), removeOne: createStateOperator(removeOneMutably), - removeMany: createStateOperator(removeManyMutably), - map: createStateOperator(mapMutably) + removeMany: createStateOperator(removeManyMutably) } } diff --git a/src/index.ts b/src/index.ts index aa484c2f2d..c61b909506 100644 --- a/src/index.ts +++ b/src/index.ts @@ -75,7 +75,6 @@ export { EntityState, EntityAdapter, Update, - EntityMap, IdSelector, Comparer } from './entities/models' diff --git a/type-tests/files/createEntityAdapter.typetest.ts b/type-tests/files/createEntityAdapter.typetest.ts new file mode 100644 index 0000000000..c53a29ff2e --- /dev/null +++ b/type-tests/files/createEntityAdapter.typetest.ts @@ -0,0 +1,113 @@ +import { + createSlice, + createEntityAdapter, + EntityAdapter, + ActionCreatorWithPayload, + ActionCreatorWithoutPayload +} from 'src' +import { EntityStateAdapter, EntityId, Update } from 'src/entities/models' + +function expectType(t: T) { + return t +} + +function extractReducers( + adapter: EntityAdapter +): Omit, 'map'> { + const { + selectId, + sortComparer, + getInitialState, + getSelectors, + ...rest + } = adapter + return rest +} + +/** + * should be usable in a slice, with all the "reducer-like" functions + */ +{ + type Entity = { + value: string + } + const adapter = createEntityAdapter() + const slice = createSlice({ + name: 'test', + initialState: adapter.getInitialState(), + reducers: { + ...extractReducers(adapter) + } + }) + + expectType>(slice.actions.addOne) + expectType>(slice.actions.addMany) + expectType>(slice.actions.setAll) + expectType>(slice.actions.removeOne) + expectType>(slice.actions.removeMany) + expectType(slice.actions.removeAll) + expectType>>(slice.actions.updateOne) + expectType[]>>( + slice.actions.updateMany + ) + expectType>(slice.actions.upsertOne) + expectType>(slice.actions.upsertMany) +} + +/** + * should not be able to mix with a different EntityAdapter + */ +{ + type Entity = { + value: string + } + type Entity2 = { + value2: string + } + const adapter = createEntityAdapter() + const adapter2 = createEntityAdapter() + createSlice({ + name: 'test', + initialState: adapter.getInitialState(), + reducers: { + addOne: adapter.addOne, + // typings:expect-error + addOne2: adapter2.addOne + } + }) +} + +/** + * should be usable in a slice with extra properties + */ +{ + type Entity = { + value: string + } + const adapter = createEntityAdapter() + createSlice({ + name: 'test', + initialState: adapter.getInitialState({ extraData: 'test' }), + reducers: { + addOne: adapter.addOne + } + }) +} + +/** + * should not be usable in a slice with an unfitting state + */ +{ + type Entity = { + value: string + } + const adapter = createEntityAdapter() + createSlice({ + name: 'test', + initialState: { somethingElse: '' }, + reducers: { + // typings:expect-error + addOne: adapter.addOne + } + }) +}