Harness-engineering redux-slice-pattern

Redux Slice Pattern

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/redux-slice-pattern" ~/.claude/skills/intense-visions-harness-engineering-redux-slice-pattern-802f34 && rm -rf "$T"
manifest: agents/skills/claude-code/redux-slice-pattern/SKILL.md
source content

Redux Slice Pattern

Organize Redux state into self-contained slices using createSlice for co-located reducers, actions, and selectors

When to Use

  • Creating a new domain of state in a Redux Toolkit application
  • Refactoring legacy Redux code that uses separate action constants, action creators, and reducers
  • Adding CRUD operations for a resource (users, posts, products)
  • Needing Immer-powered immutable updates without boilerplate

Instructions

  1. One slice per domain concern. Name the file
    <domain>.slice.ts
    and keep it in
    slices/
    or
    features/<domain>/
    .
  2. Define the interface for the slice state explicitly — never rely on inference from
    initialState
    alone.
  3. Use
    createSlice
    with a unique
    name
    that matches the store key. This name prefixes all generated action types.
  4. Write reducers as mutating logic — Immer converts them to immutable updates. Never return and mutate in the same reducer.
  5. Export the generated action creators and the reducer separately. Export selectors from the same file.
  6. Use
    prepare
    callbacks when actions need payload transformation (adding IDs, timestamps, normalization).
  7. Handle related async actions from thunks via
    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;

Details

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:

  • Putting derived data in state (compute it in selectors instead)
  • Giant slices that own unrelated concerns (split them)
  • Using the deprecated object map notation for
    extraReducers
  • Forgetting to export actions (leads to silent bugs where dispatches do nothing)

Source

https://redux-toolkit.js.org/api/createSlice

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. Verify your implementation against the details and edge cases listed above.

Harness Integration

  • Type: knowledge — this skill is a reference document, not a procedural workflow.
  • No tools or state — consumed as context by other skills and agents.

Success Criteria

  • The patterns described in this document are applied correctly in the implementation.
  • Edge cases and anti-patterns listed in this document are avoided.