From harness-claude
Normalize Redux entity collections using createEntityAdapter for O(1) lookups by ID, pre-built CRUD reducers, and selectors. Use for managing users, products, or messages with unique IDs.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Normalize entity collections with createEntityAdapter for O(1) lookups and pre-built CRUD reducers
Organizes Redux state into self-contained slices using createSlice for co-located reducers, actions, and selectors. Use when creating new state domains, refactoring legacy code, or adding CRUD operations.
Guides Redux and Redux Toolkit for global state management: slices, stores, actions, reducers, hooks, selectors, middleware, async thunks.
Generates Redux slices and operations for frontend state management. Auto-activates on phrases like 'redux slice generator', 'redux generator', 'redux'. Useful for React-based projects needing quick slice boilerplate.
Share bugs, ideas, or general feedback.
Normalize entity collections with createEntityAdapter for O(1) lookups and pre-built CRUD reducers
addOne, updateOne, removeOne reducers without writing them manuallycreateEntityAdapter<Entity>(). Provide selectId if the ID field is not id, and sortComparer if the collection should be kept sorted.adapter.getInitialState() to generate the initial { ids: [], entities: {} } shape. Pass additional state fields as an argument.createSlice, or call them inside reducer functions.adapter.getSelectors() to generate selectAll, selectById, selectIds, selectEntities, and selectTotal.upsertMany for bulk operations (API responses) — it adds new entities and updates existing ones.// features/users/users.slice.ts
import { createSlice, createEntityAdapter, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from '../../store';
import { fetchUsers } from './users.thunks';
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'member';
}
const usersAdapter = createEntityAdapter<User>({
sortComparer: (a, b) => a.name.localeCompare(b.name),
});
interface UsersExtraState {
status: 'idle' | 'loading' | 'succeeded' | 'failed';
error: string | null;
}
const initialState = usersAdapter.getInitialState<UsersExtraState>({
status: 'idle',
error: null,
});
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {
userAdded: usersAdapter.addOne,
userUpdated: usersAdapter.updateOne,
userRemoved: usersAdapter.removeOne,
usersCleared: usersAdapter.removeAll,
},
extraReducers: (builder) => {
builder.addCase(fetchUsers.fulfilled, (state, action) => {
usersAdapter.upsertMany(state, action.payload);
state.status = 'succeeded';
});
},
});
export const { userAdded, userUpdated, userRemoved, usersCleared } = usersSlice.actions;
export default usersSlice.reducer;
// Selectors — pass the slice state selector to scope them
export const {
selectAll: selectAllUsers,
selectById: selectUserById,
selectIds: selectUserIds,
selectTotal: selectTotalUsers,
} = usersAdapter.getSelectors<RootState>((state) => state.users);
Normalized shape: The adapter stores { ids: string[], entities: Record<string, Entity> }. The ids array controls display order; entities provides O(1) lookup. This is the same normalization pattern from the normalizr library.
CRUD methods: Each method has single and batch variants:
addOne / addMany — insert if not present, no-op if ID existssetOne / setMany / setAll — replace entirely (add or overwrite)upsertOne / upsertMany — add or shallow mergeupdateOne / updateMany — apply a partial update to an existing entity (uses { id, changes })removeOne / removeMany / removeAllupdateOne shape: Pass { id: string, changes: Partial<Entity> }, not the full entity:
dispatch(userUpdated({ id: '1', changes: { name: 'New Name' } }));
Custom ID field: If your entity uses _id or uuid instead of id:
const adapter = createEntityAdapter<Entity>({ selectId: (entity) => entity._id });
Sorting: sortComparer keeps ids sorted after every CRUD operation. Omit it if sort order does not matter — unsorted is faster for large collections.
https://redux-toolkit.js.org/api/createEntityAdapter