From harness-claude
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.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Organize Redux state into self-contained slices using createSlice for co-located reducers, actions, and selectors
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.
Types Redux Toolkit state, actions, thunks, hooks, and middleware with full TypeScript inference and minimal annotations. Use for setup, fixing type errors in slices, extraReducers, or components.
Share bugs, ideas, or general feedback.
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