Normalize Redux collections with createEntityAdapter
Redux arrays become costly to update at scale. createEntityAdapter normalizes state to ids and entities, delivering generated reducers and memoized selectors for predictable collection management.
04 Sept 2026, 17:28 UTC

What createEntityAdapter gives you
Updating one item in a large Redux array often means replacing the whole list. Components that select the array re-render, and manual immutable updates invite bugs. The practical takeaway: store collections as normalized data with ids and entities, and let createEntityAdapter generate the update logic.
createEntityAdapter is a Redux Toolkit utility, stable in Redux Toolkit 1.x and later. It builds a normalized state shape with two top-level fields: ids (an array of identifiers) and entities (a map from id to the full record). This shape lets you update a single item without touching the rest of the collection, and the adapter generates reducers for add, upsert, remove, and update operations.
The adapter also creates selectors—selectAll, selectById, selectIds, selectTotal—designed for stable memoization when used with the slice's state. Immer, which Redux Toolkit uses internally, lets reducer code appear mutable while the produced state remains immutable.
Worked example: a projects slice
// projectsSlice.js
import { createSlice, createEntityAdapter, configureStore } from '@reduxjs/toolkit'
const projectsAdapter = createEntityAdapter({
selectId: (project) => project.id,
sortComparer: (a, b) => a.name.localeCompare(b.name)
})
const initialState = projectsAdapter.getInitialState()
const projectsSlice = createSlice({
name: 'projects',
initialState,
reducers: {
projectAdded: projectsAdapter.addOne,
projectsReceived: projectsAdapter.setAll,
projectUpdated: projectsAdapter.updateOne,
projectRemoved: projectsAdapter.removeOne
},
extraReducers: (builder) => {
builder.addCase('projects/fetch/fulfilled', (state, action) => {
projectsAdapter.upsertMany(state, action.payload)
})
}
})
export const { projectAdded, projectsReceived, projectUpdated, projectRemoved } = projectsSlice.actions
export const {
selectAll: selectAllProjects,
selectById: selectProjectById,
selectIds: selectProjectIds
} = projectsAdapter.getSelectors((state) => state.projects)
export default projectsSlice.reducerStore setup
// store.js
import { configureStore } from '@reduxjs/toolkit'
import projectsReducer from './projectsSlice'
export const store = configureStore({
reducer: {
projects: projectsReducer
}
})Trade-off and when not to normalize
Normalization adds indirection. You read via selectors instead of direct array access, and the state shape is less obvious to newcomers than a plain array. For small, read-only, or rarely updated lists, the extra mental model can reduce readability without clear benefit.
Over-normalizing deeply nested relational data can create many slices and selector chains that are harder to trace. The decision to normalize is most valuable for collections with frequent partial updates and relational access where stable identity matters.
Limitations to keep in mind: sortComparer behavior is version-sensitive. Verify the default ordering and comparer options against the Redux Toolkit version in use. Adapter reducers assume the id field you configure via selectId exists and is unique.
How to verify the result
After creating a minimal store with the slice, dispatch an upsertMany action and inspect the state in Redux DevTools. The expected shape is an object with an ids array and an entities map, not a raw array.
Run adapter selectors selectAll and selectById against the store state to confirm mapping. selectAll should return an array of entities ordered by ids. selectById should return the entity for a given id or undefined. This check confirms the adapter reducers produced the expected normalized shape without manual mutation.
Use normalization when collection updates are frequent and identity stability matters. Keep simple lists as arrays. The adapter removes boilerplate for the normalized case while keeping updates declarative.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.