From harness-claude
Organizes Redux state into self-contained slices using createSlice for co-located reducers, actions, and selectors. Useful for adding new state domains or refactoring legacy Redux code.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:redux-slice-patternThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Organize Redux state into self-contained slices using createSlice for co-located reducers, actions, and selectors
Organize Redux state into self-contained slices using createSlice for co-located reducers, actions, and selectors
<domain>.slice.ts and keep it in slices/ or features/<domain>/.initialState alone.createSlice with a unique name that matches the store key. This name prefixes all generated action types.prepare callbacks when actions need payload transformation (adding IDs, timestamps, normalization).extraReducers using the builder callback pattern — never the object notation (it is deprecated).// features/todos/todos.slice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface Todo {
id: string;
title: string;
completed: boolean;
}
interface TodosState {
items: Todo[];
filter: 'all' | 'active' | 'completed';
}
const initialState: TodosState = {
items: [],
filter: 'all',
};
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
addTodo: {
reducer(state, action: PayloadAction<Todo>) {
state.items.push(action.payload);
},
prepare(title: string) {
return { payload: { id: crypto.randomUUID(), title, completed: false } };
},
},
toggleTodo(state, action: PayloadAction<string>) {
const todo = state.items.find((t) => t.id === action.payload);
if (todo) todo.completed = !todo.completed;
},
setFilter(state, action: PayloadAction<TodosState['filter']>) {
state.filter = action.payload;
},
},
});
export const { addTodo, toggleTodo, setFilter } = todosSlice.actions;
export default todosSlice.reducer;
// Co-located selectors
export const selectTodos = (state: { todos: TodosState }) => state.todos.items;
export const selectFilter = (state: { todos: TodosState }) => state.todos.filter;
Immer rules: You can either mutate state directly or return a new value — never both. Returning undefined is not the same as not returning; if you need a no-op, just don't write a return statement.
Naming conventions: The name field in createSlice becomes the action type prefix (todos/addTodo). Keep it short, lowercase, and matching the store mount point.
extraReducers vs reducers: Use reducers for actions owned by this slice. Use extraReducers for actions owned elsewhere (thunks, other slices). The builder callback pattern provides full TypeScript inference:
extraReducers: (builder) => {
builder
.addCase(fetchTodos.fulfilled, (state, action) => {
state.items = action.payload;
})
.addCase(fetchTodos.rejected, (state, action) => {
state.error = action.error.message ?? 'Failed';
});
};
Anti-patterns to avoid:
extraReducershttps://redux-toolkit.js.org/api/createSlice
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeTypes Redux state, actions, thunks, and hooks with full inference using createAsyncThunk, typed hooks, and builder callbacks. Helps fix type errors in RTK projects.
Provides Zustand 5.x patterns for React state management: slices, middleware, Immer, useShallow, persistence, selectors, devtools, async actions, and anti-patterns with TanStack Query integration. Use for global client state without boilerplate.
Guides React state management with Redux Toolkit, Zustand, Jotai, and React Query. Use when setting up global state, managing server state, or choosing between solutions.