new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Redux Toolkit · API · all subjects

createentityadapter

60 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Comparer type definition

Comparer<T> is a type defined as '(a: T, b: T) => number', used for comparison functions that accept two entities and return a numeric result.

IdSelector type definition

IdSelector<T> is a type defined as '(model: T) => EntityId', representing a function that takes an entity and returns its ID.

createEntityAdapter purpose and overview

createEntityAdapter is a function that generates a set of prebuilt reducers and selectors for performing CRUD operations on a normalized state structure containing instances of a particular type of data object. The reducer functions may be passed as case reducers to createReducer and createSlice, or used as mutating helper functions inside createReducer and createSlice. This API was ported from the @ngrx/entity library created by the NgRx maintainers.

Entity state structure format

The entity state structure that createEntityAdapter manages has the following format: an object with an 'ids' property containing an array of unique IDs (must be strings or numbers), and an 'entities' property containing a lookup table (object) mapping entity IDs to the corresponding entity objects.

createEntityAdapter parameters

createEntityAdapter accepts a single options object parameter with two optional fields: selectId (a function that accepts a single Entity instance and returns the value of the unique ID field, with default implementation 'entity => entity.id') and sortComparer (a callback function that accepts two Entity instances and returns a standard Array.sort() numeric result to indicate their relative order for sorting). If sortComparer is provided, the state.ids array will be kept in sorted order. Sorting only kicks in when state is changed via CRUD functions like addOne() or updateMany().

createEntityAdapter return value overview

createEntityAdapter returns a plain JS object (not a class) called an entity adapter containing: generated reducer functions, the original provided selectId and sortComparer callbacks, a method to generate an initial entity state value, and functions to generate a set of globalized and non-globalized memoized selector functions for the entity type.

Update type definition

Update<T> is a type defined as '{ id: EntityId; changes: Partial<T> }', used to describe an update operation that specifies an entity ID and an object containing one or more new field values to update.

EntityState interface definition

EntityState<T> interface is defined with two properties: ids (type EntityId[], an array of unique entity IDs) and entities (type Record<EntityId, T>, a lookup table mapping entity IDs to entity objects).

EntityDefinition interface definition

EntityDefinition<T> interface contains two properties: selectId (type IdSelector<T>, a function to extract the ID from an entity) and sortComparer (type 'false | Comparer<T>', either false if no sorting is applied, or a comparison function).

EntityStateAdapter CRUD methods signatures

The EntityStateAdapter<T> interface includes the following CRUD methods: addOne: accepts either (state: S, entity: T) or (state: S, action: PayloadAction<T>), returns S addMany: accepts either (state: S, entities: T[]) or (state: S, entities: PayloadAction<T[]>), returns S setOne: accepts either (state: S, entity: T) or (state: S, action: PayloadAction<T>), returns S setMany: accepts either (state: S, entities: T[]) or (state: S, entities: PayloadAction<T[]>), returns S setAll: accepts either (state: S, entities: T[]) or (state: S, entities: PayloadAction<T[]>), returns S removeOne: accepts either (state: S, key: EntityId) or (state: S, key: PayloadAction<EntityId>), returns S removeMany: accepts either (state: S, keys: EntityId[]) or (state: S, keys: PayloadAction<EntityId[]>), returns S removeAll: accepts (state: S), returns S updateOne: accepts either (state: S, update: Update<T>) or (state: S, update: PayloadAction<Update<T>>), returns S updateMany: accepts either (state: S, updates: Update<T>[]) or (state: S, updates: PayloadAction<Update<T>[]>), returns S upsertOne: accepts either (state: S, entity: T) or (state: S, entity: PayloadAction<T>), returns S upsertMany: accepts either (state: S, entities: T[]) or (state: S, entities: PayloadAction<T[]>), returns S

EntitySelectors interface definition

EntitySelectors<T, V> interface contains the following selector functions: selectIds (accepts state: V, returns EntityId[]), selectEntities (accepts state: V, returns Record<EntityId, T>), selectAll (accepts state: V, returns T[]), selectTotal (accepts state: V, returns number), and selectById (accepts state: V and id: EntityId, returns T | undefined).

addOne CRUD function behavior

addOne accepts a single entity and adds it if it's not already present. It accepts either a plain entity object or a PayloadAction with the entity as payload.

addMany CRUD function behavior

addMany accepts an array of entities or an object in the shape of Record<EntityId, T>, and adds them if not already present. It accepts either plain entities or a PayloadAction with the entities as payload.

setOne CRUD function behavior

setOne accepts a single entity and adds or replaces it. If an entity with that ID already exists, it will be completely replaced with the new one.

setMany CRUD function behavior

setMany accepts an array of entities or an object in the shape of Record<EntityId, T>, and adds or replaces them. Existing entities with matching IDs will be completely replaced.

setAll CRUD function behavior

setAll accepts an array of entities or an object in the shape of Record<EntityId, T>, and replaces all existing entities with the values in the array. This clears out any entities that were previously stored.

removeOne CRUD function behavior

removeOne accepts a single entity ID value, and removes the entity with that ID if it exists.

removeMany CRUD function behavior

removeMany accepts an array of entity ID values, and removes each entity with those IDs if they exist.

removeAll CRUD function behavior

removeAll removes all entities from the entity state object. It takes only the state parameter and returns an empty state.

updateOne CRUD function behavior

updateOne accepts an update object containing an entity ID and an object containing one or more new field values to update inside a 'changes' field, and performs a shallow update on the corresponding entity. It accepts either a plain Update<T> object or a PayloadAction with the update as payload.

updateMany CRUD function behavior

updateMany accepts an array of update objects, and performs shallow updates on all corresponding entities. It accepts either plain Update<T>[] objects or a PayloadAction with the updates as payload.

upsertOne CRUD function behavior

upsertOne accepts a single entity. If an entity with that ID exists, it will perform a shallow update and the specified fields will be merged into the existing entity, with matching fields overwriting existing values. If the entity does not exist, it will be added.

upsertMany CRUD function behavior

upsertMany accepts an array of entities or an object in the shape of Record<EntityId, T> that will be shallowly upserted. For each entity, if it exists it will be shallow updated with matching fields overwriting existing values, or if it does not exist it will be added.

Difference between add, set, and upsert operations

When an entity already exists: addOne and addMany will do nothing with the new entity; setOne and setMany will completely replace the old entity with the new one and remove any properties not present in the new version; upsertOne and upsertMany will do a shallow copy to merge the old and new entities overwriting existing values, adding any that were not there and not touching properties not provided in the new entity.

CRUD method usage with immutability

CRUD methods may be used in multiple ways: passed as case reducers directly to createReducer and createSlice, used as mutating helper methods when called manually if the state argument is an Immer Draft value, or used as immutable update methods when called manually if the state argument is a plain JS object or array. The methods check if the state argument is an Immer Draft; if it is, they assume it's safe to continue mutating. If not, they pass the value to Immer's createNextState() and return the immutably updated result.

CRUD method argument types

The argument to each CRUD method may be either a plain value (such as a single Entity object for addOne or an Entity[] array for addMany) or a PayloadAction action object with that same value as action.payload. This enables using them as both helper functions and reducers.

CRUD methods do not create Redux actions automatically

CRUD methods do not have corresponding Redux actions created automatically. They are just standalone reducers and update logic. It is entirely up to the developer to decide where and how to use these methods. Most of the time, they will want to pass them to createSlice or use them inside another reducer.

Shallow update behavior in updateOne and updateMany

updateOne, updateMany, upsertOne, and upsertMany only perform shallow updates. This means that if an update consists of an object that includes nested properties, the value of the incoming change will overwrite the entire existing nested object. These methods are best used with normalized data that do not have nested properties.

getInitialState method overview

getInitialState returns a new entity state object like {ids: [], entities: {}}. It accepts an optional object argument whose fields will be merged into the returned initial state value. It can also accept a second parameter of an array of entities or a Record<EntityId, T> object to pre-populate the initial state.

getInitialState with additional fields example

getInitialState can accept an object with additional state fields to track. For example: booksAdapter.getInitialState({ loading: 'idle' }) returns { ids: [], entities: {}, loading: 'idle' }.

getInitialState with pre-populated entities

getInitialState can pre-populate entities with a second parameter: booksAdapter.getInitialState({ loading: 'idle' }, [{ id: 'a', title: 'First' }, { id: 'b', title: 'Second' }]). The first parameter can be undefined if no additional properties are needed.

getSelectors method purpose

The entity adapter contains a getSelectors() function that returns a set of selectors that know how to read the contents of an entity state object. Each selector function is created using the createSelector function from Reselect to enable memoizing calculation of the results.

selectIds selector function

selectIds is a selector function that returns the state.ids array.

selectEntities selector function

selectEntities is a selector function that returns the state.entities lookup table.

selectAll selector function

selectAll is a selector function that maps over the state.ids array and returns an array of entities in the same order.

selectTotal selector function

selectTotal is a selector function that returns the total number of entities being stored in the state.

selectById selector function

selectById is a selector function that accepts the state and an entity ID, and returns the entity with that ID or undefined.

getSelectors without arguments

When called without any arguments or with undefined as the first parameter, getSelectors() returns an unglobalized set of selector functions that assume their state argument is the actual entity state object to read from.

getSelectors with state selector function

getSelectors may be called with a selector function that accepts the entire Redux state tree and returns the correct entity state object. This creates a globalized set of selectors that already know how to find the entity state.

getSelectors with custom createSelector

The createSelector instance used by getSelectors can be replaced by passing it as part of the options object (second parameter). For example: booksAdapter.getSelectors(undefined, { createSelector: createWeakMapDraftSafeSelector }). If no instance is passed, it defaults to createDraftSafeSelector.

updateMany with multiple updates to same ID behavior

If updateMany() is called with multiple updates targeted to the same ID, they will be merged into a single update, with later updates overwriting the earlier ones.

Update ID conflict behavior

For both updateOne() and updateMany(), changing the ID of one existing entity to match the ID of a second existing entity will cause the first to replace the second completely.

Update with no matching entity behavior

If there is no item for the ID being updated in updateOne() or updateMany(), the update will be silently ignored.

createEntityAdapter call reuse in JavaScript

In plain JavaScript, a single adapter definition may be able to be reused with multiple entity types if they're similar enough (such as all having an entity.id field). However, for TypeScript usage, createEntityAdapter must be called separately for each distinct Entity type so that type definitions are inferred correctly.

Only plain JS objects and arrays in state

Only plain JS objects and arrays should be passed in to the store. Class instances are not allowed in Redux state.

Entity definition terminology

Entity refers to a unique type of data object in an application (for example, User, Post, or Comment in a blogging application). Each unique instance of an entity is assumed to have a unique ID value in a specific field. The term Entity (capitalized) refers to the specific data type being managed, while entity (lowercase) refers to a single instance of that type.

createEntityAdapter full example with CRUD and selectors

Example showing createEntityAdapter usage with CRUD methods and selectors: ```js import { createEntityAdapter, createSlice, configureStore, } from '@reduxjs/toolkit' const booksAdapter = createEntityAdapter({ sortComparer: (a, b) => a.title.localeCompare(b.title), }) const booksSlice = createSlice({ name: 'books', initialState: booksAdapter.getInitialState({ loading: 'idle', }), reducers: { bookAdded: booksAdapter.addOne, booksLoading(state, action) { if (state.loading === 'idle') { state.loading = 'pending' } }, booksReceived(state, action) { if (state.loading === 'pending') { booksAdapter.setAll(state, action.payload) state.loading = 'idle' } }, bookUpdated: booksAdapter.updateOne, }, }) const { bookAdded, booksLoading, booksReceived, bookUpdated } = booksSlice.actions const store = configureStore({ reducer: { books: booksSlice.reducer, }, }) console.log(store.getState().books) // {ids: [], entities: {}, loading: 'idle' } const booksSelectors = booksAdapter.getSelectors((state) => state.books) store.dispatch(bookAdded({ id: 'a', title: 'First' })) console.log(store.getState().books) // {ids: ["a"], entities: {a: {id: "a", title: "First"}}, loading: 'idle' } store.dispatch(bookUpdated({ id: 'a', changes: { title: 'First (altered)' } })) store.dispatch(booksLoading()) console.log(store.getState().books) // {ids: ["a"], entities: {a: {id: "a", title: "First (altered)"}}, loading: 'pending' } store.dispatch( booksReceived([ { id: 'b', title: 'Book 3' }, { id: 'c', title: 'Book 2' }, ]), ) console.log(booksSelectors.selectIds(store.getState())) // ["c", "b"] ("a" was removed due to setAll, sorted by title) console.log(booksSelectors.selectAll(store.getState())) // [{id: "c", title: "Book 2"}, {id: "b", title: "Book 3"}] ```

createEntityAdapter with custom selectId example

Example showing createEntityAdapter with a custom selectId function: ```ts import { createEntityAdapter, createSlice, configureStore, } from '@reduxjs/toolkit' type Book = { bookId: string; title: string } const booksAdapter = createEntityAdapter({ selectId: (book: Book) => book.bookId, sortComparer: (a, b) => a.title.localeCompare(b.title), }) const booksSlice = createSlice({ name: 'books', initialState: booksAdapter.getInitialState(), reducers: { bookAdded: booksAdapter.addOne, booksReceived(state, action) { booksAdapter.setAll(state, action.payload.books) }, }, }) const store = configureStore({ reducer: { books: booksSlice.reducer, }, }) type RootState = ReturnType<typeof store.getState> console.log(store.getState().books) // { ids: [], entities: {} } const booksSelectors = booksAdapter.getSelectors<RootState>( (state) => state.books, ) const allBooks = booksSelectors.selectAll(store.getState()) ```

createEntityAdapter update functions with Immer

Redux Toolkit's createEntityAdapter update functions can be used as standalone reducers or as mutating update functions. These functions determine whether to mutate or return a new value by checking if the state they are given is wrapped in a draft or not. When calling these functions inside a case reducer, ensure you know whether you are passing them a draft value or a plain value.

entityAdapter.getSelectors accepts createSelector option

In Redux Toolkit 2.0, entityAdapter.getSelectors() now accepts an options object as its second argument. This allows passing a custom createSelector method, which will be used to memoize the generated selectors. This is useful for using alternate memoizers from Reselect or other memoization libraries.

createEntityAdapter generates CRUD operations

createEntityAdapter provides a standardized way to store collections as { ids: [], entities: {} }. It generates reducer functions for common operations like removeOne, removeMany, upsertOne, upsertMany, updateOne, updateMany, setAll, addMany, and addOne.

createEntityAdapter getInitialState

createEntityAdapter returns an adapter object with a getInitialState() method that returns the default normalized state shape of { ids: [], entities: {} }. You can also pass an object to getInitialState() to add additional properties to the initial state.

createEntityAdapter selectId option

By default, createEntityAdapter assumes entities have an `id` field as the unique identifier. If your data uses a different field name, pass a `selectId` option: `createEntityAdapter({ selectId: (user) => user.idx })`. This tells the adapter which field contains the unique identifier.

createEntityAdapter sortComparer option

createEntityAdapter accepts a `sortComparer` option that takes a comparison function like you'd pass to Array.sort(). This sorts the `ids` array in state. Example: `createEntityAdapter({ sortComparer: (a, b) => a.first_name.localeCompare(b.first_name) })`.

createEntityAdapter CRUD methods accept arrays or normalized objects

Methods like setAll, addMany, and upsertMany on a createEntityAdapter accept either an array of entities or a normalized object in the shape { id: entity }. This makes it easier to work with pre-normalized data from libraries like normalizr.

createEntityAdapter getSelectors generates common selectors

createEntityAdapter provides a getSelectors() method that takes a selector function to access the slice state and returns selector functions: selectById, selectIds, selectEntities, selectAll, and selectTotal. Example: `const { selectAllUsers, selectUserById } = usersAdapter.getSelectors((state) => state.users)`

createEntityAdapter with id property

If entities are normalized by an id property, createEntityAdapter only requires the entity type as a single generic argument: createEntityAdapter<Book>(). No selectId function is needed.

createEntityAdapter with custom selectId

If entities use a different property for normalization, pass a custom selectId function and annotate the entity type there: createEntityAdapter({ selectId: (book: Book) => book.bookId }). This allows proper inference of the ID type.

createEntityAdapter methods in reducers

Use createEntityAdapter methods like addOne, addMany, setAll, upsertMany in slice reducers. Methods can be passed directly as reducers or called on state in extra reducers.

createEntityAdapter with normalizr library

When using normalizr, pass the entities portion directly to methods like addMany, upsertMany, and setAll without conversion. However, normalizr TypeScript typings may not reflect multiple data types, so manually specify the normalized data shape as a generic argument to normalize<any, NormalizedShape>().

Give your agent this brain