Lesson 13 / 25
Normalising Data with createEntityAdapter
ids plus a lookup table.
Store each item once
Nested or duplicated data is hard to update consistently. Normalised state keeps each collection as { ids: [], entities: {} }: an ordered array of IDs and an object mapping ID to item, with relations stored as IDs. createEntityAdapter produces this shape with getInitialState(), provides CRUD reducer helpers (addOne, addMany, setAll, upsertMany, updateOne, removeOne and more) usable directly as case reducers or inside them, and generates selectors (selectAll, selectById, selectIds, selectEntities, selectTotal). An optional sortComparer keeps ids sorted.
Shape state for fast lookups
Normalised data, memoised selectors and visible middleware keep big stores manageable.
A todos adapter
Adapter, slice and selectors.
import { createEntityAdapter, createSlice, type PayloadAction } from '@reduxjs/toolkit';
type Todo = { id: string; text: string; done: boolean };
const todosAdapter = createEntityAdapter<Todo>({
sortComparer: (a, b) => a.text.localeCompare(b.text),
});
const todosSlice = createSlice({
name: 'todos',
initialState: todosAdapter.getInitialState({ filter: 'all' as 'all' | 'open' }),
reducers: {
todoAdded: todosAdapter.addOne,
todosLoaded: todosAdapter.setAll,
todoToggled(state, action: PayloadAction<string>) {
const todo = state.entities[action.payload];
if (todo) todo.done = !todo.done;
},
},
});
export const { selectAll: selectAllTodos, selectById: selectTodoById } =
todosAdapter.getSelectors((state: RootState) => state.todos);A library catalogue
Books sit once on numbered shelves (entities); reading lists only hold catalogue numbers (ids). Fix a typo in a title and every list that refers to it is correct.
Quick check: What shape does createEntityAdapter give a collection?
- A nested tree of objects
- An ids array plus an entities lookup object
- A Map stored in state
- A plain array only
Answer
An ids array plus an entities lookup object — Normalised state makes lookups and updates O(1) by ID.