A list of records in an array makes every update a scan. createEntityAdapter normalizes the collection to { ids: [], entities: {} } and generates the CRUD reducers and selectors for it.
const adapter = createEntityAdapter({
sortComparer: (a, b) => a.name.localeCompare(b.name), // keeps ids sorted
});
const products = createSlice({
name: 'products', initialState: adapter.getInitialState({ status: 'idle' }),
reducers: { loaded: adapter.setAll, added: adapter.addOne, // CRUD reducers,
renamed: adapter.updateOne, dropped: adapter.removeOne }, // used directly
});
const { loaded, added, renamed, dropped } = products.actions;
const store = configureStore({ reducer: { products: products.reducer } });
const selectors = adapter.getSelectors(state => state.products);
store.dispatch(loaded([{ id: 2, name: 'Mug' }, { id: 7, name: 'Apron' }]));
store.dispatch(added({ id: 4, name: 'Cap' }));
store.dispatch(renamed({ id: 2, changes: { name: 'Zinc mug' } })); store.dispatch(dropped(7));
const s = store.getState();
console.log(s.products.ids, s.products.entities);
console.log(selectors.selectAll(s).map(p => p.name), selectors.selectTotal(s));[ 4, 2 ] { '2': { id: 2, name: 'Zinc mug' }, '4': { id: 4, name: 'Cap' } }
[ 'Cap', 'Zinc mug' ] 2Reading one record is now a property lookup instead of a scan, and — the real payoff — updating one record leaves every other record's object identity intact, so a memoized row does not re-render because its neighbor changed. getSelectors(rootSelector) returns selectAll, selectById, selectIds, selectEntities and selectTotal, already memoized.
The generated reducers come in families, each with a One and a Many form: add ignores an id already present, set replaces, upsert shallow-merges, update takes { id, changes }, and remove deletes. One option you will need early is selectId (e => e._id), for MongoDB 1,815 documents keyed on _id (MongoDB).