Skip to content

Commit

Permalink
Merge pull request #4183 from reduxjs/getinitialstate-entities
Browse files Browse the repository at this point in the history
Add second parameter to getInitialState to prefill entities
  • Loading branch information
EskiMojo14 committed Feb 12, 2024
2 parents 5ea4c31 + cb6717d commit 23f1114
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 39 deletions.
41 changes: 15 additions & 26 deletions packages/toolkit/src/entities/create_adapter.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
import type {
EntityDefinition,
Comparer,
IdSelector,
EntityAdapter,
EntityId,
} from './models'
import type { EntityAdapter, EntityId, EntityAdapterOptions } from './models'
import { createInitialStateFactory } from './entity_state'
import { createSelectorsFactory } from './state_selectors'
import { createSortedStateAdapter } from './sorted_state_adapter'
import { createUnsortedStateAdapter } from './unsorted_state_adapter'
import type { WithRequiredProp } from '../tsHelpers'

export interface EntityAdapterOptions<T, Id extends EntityId> {
selectId?: IdSelector<T, Id>
sortComparer?: false | Comparer<T>
}

export function createEntityAdapter<T, Id extends EntityId>(options: {
selectId: IdSelector<T, Id>
sortComparer?: false | Comparer<T>
}): EntityAdapter<T, Id>
export function createEntityAdapter<T, Id extends EntityId>(
options: WithRequiredProp<EntityAdapterOptions<T, Id>, 'selectId'>,
): EntityAdapter<T, Id>

export function createEntityAdapter<T extends { id: EntityId }>(options?: {
sortComparer?: false | Comparer<T>
}): EntityAdapter<T, T['id']>
export function createEntityAdapter<T extends { id: EntityId }>(
options?: Omit<EntityAdapterOptions<T, T['id']>, 'selectId'>,
): EntityAdapter<T, T['id']>

/**
*
Expand All @@ -31,22 +20,22 @@ export function createEntityAdapter<T extends { id: EntityId }>(options?: {
* @public
*/
export function createEntityAdapter<T>(
options: {
selectId?: IdSelector<T, EntityId>
sortComparer?: false | Comparer<T>
} = {},
options: EntityAdapterOptions<T, EntityId> = {},
): EntityAdapter<T, EntityId> {
const { selectId, sortComparer }: EntityDefinition<T, EntityId> = {
const {
selectId,
sortComparer,
}: Required<EntityAdapterOptions<T, EntityId>> = {
sortComparer: false,
selectId: (instance: any) => instance.id,
...options,
}

const stateFactory = createInitialStateFactory<T, EntityId>()
const selectorsFactory = createSelectorsFactory<T, EntityId>()
const stateAdapter = sortComparer
? createSortedStateAdapter(selectId, sortComparer)
: createUnsortedStateAdapter(selectId)
const stateFactory = createInitialStateFactory(stateAdapter)
const selectorsFactory = createSelectorsFactory<T, EntityId>()

return {
selectId,
Expand Down
25 changes: 20 additions & 5 deletions packages/toolkit/src/entities/entity_state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { EntityId, EntityState } from './models'
import type {
EntityId,
EntityState,
EntityStateAdapter,
EntityStateFactory,
} from './models'

export function getInitialEntityState<T, Id extends EntityId>(): EntityState<
T,
Expand All @@ -10,13 +15,23 @@ export function getInitialEntityState<T, Id extends EntityId>(): EntityState<
}
}

export function createInitialStateFactory<T, Id extends EntityId>() {
function getInitialState(): EntityState<T, Id>
export function createInitialStateFactory<T, Id extends EntityId>(
stateAdapter: EntityStateAdapter<T, Id>,
): EntityStateFactory<T, Id> {
function getInitialState(
state?: undefined,
entities?: readonly T[] | Record<Id, T>,
): EntityState<T, Id>
function getInitialState<S extends object>(
additionalState: S,
entities?: readonly T[] | Record<Id, T>,
): EntityState<T, Id> & S
function getInitialState(additionalState: any = {}): any {
return Object.assign(getInitialEntityState(), additionalState)
function getInitialState(
additionalState: any = {},
entities?: readonly T[] | Record<Id, T>,
): any {
const state = Object.assign(getInitialEntityState(), additionalState)
return entities ? stateAdapter.setAll(state, entities) : state
}

return { getInitialState }
Expand Down
28 changes: 20 additions & 8 deletions packages/toolkit/src/entities/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ export interface EntityState<T, Id extends EntityId> {
/**
* @public
*/
export interface EntityDefinition<T, Id extends EntityId> {
selectId: IdSelector<T, Id>
sortComparer: false | Comparer<T>
export interface EntityAdapterOptions<T, Id extends EntityId> {
selectId?: IdSelector<T, Id>
sortComparer?: false | Comparer<T>
}

export type PreventAny<S, T, Id extends EntityId> = CastAny<
Expand Down Expand Up @@ -166,15 +166,27 @@ export interface EntitySelectors<T, V, Id extends EntityId> {
selectById: (state: V, id: Id) => Compute<UncheckedIndexedAccess<T>>
}

/**
* @public
*/
export interface EntityStateFactory<T, Id extends EntityId> {
getInitialState(
state?: undefined,
entities?: Record<Id, T> | readonly T[],
): EntityState<T, Id>
getInitialState<S extends object>(
state: S,
entities?: Record<Id, T> | readonly T[],
): EntityState<T, Id> & S
}

/**
* @public
*/
export interface EntityAdapter<T, Id extends EntityId>
extends EntityStateAdapter<T, Id> {
selectId: IdSelector<T, Id>
sortComparer: false | Comparer<T>
getInitialState(): EntityState<T, Id>
getInitialState<S extends object>(state: S): EntityState<T, Id> & S
extends EntityStateAdapter<T, Id>,
EntityStateFactory<T, Id>,
Required<EntityAdapterOptions<T, Id>> {
getSelectors(
selectState?: undefined,
options?: GetSelectorsOptions,
Expand Down
21 changes: 21 additions & 0 deletions packages/toolkit/src/entities/tests/entity_state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,27 @@ describe('Entity State', () => {
})
})

it('should let you provide initial entities', () => {
const book1: BookModel = { id: 'a', title: 'First' }

const initialState = adapter.getInitialState(undefined, [book1])

expect(initialState).toEqual({
ids: [book1.id],
entities: { [book1.id]: book1 },
})

const additionalProperties = { isHydrated: true }

const initialState2 = adapter.getInitialState(additionalProperties, [book1])

expect(initialState2).toEqual({
...additionalProperties,
ids: [book1.id],
entities: { [book1.id]: book1 },
})
})

it('should allow methods to be passed as reducers', () => {
const upsertBook = createAction<BookModel>('otherBooks/upsert')

Expand Down

0 comments on commit 23f1114

Please sign in to comment.