Harness-engineering redux-thunk-pattern

Redux Thunk 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/codex/redux-thunk-pattern" ~/.claude/skills/intense-visions-harness-engineering-redux-thunk-pattern-4c3713 && rm -rf "$T"
manifest: agents/skills/codex/redux-thunk-pattern/SKILL.md
source content

Redux Thunk Pattern

Handle async operations with createAsyncThunk for structured pending/fulfilled/rejected lifecycle management

When to Use

  • Fetching data from an API and storing it in Redux state
  • Performing async operations that need loading/error state tracking
  • Dispatching multiple actions in sequence (fetch then transform)
  • When RTK Query is overkill (simple one-off fetches, non-REST operations)

Instructions

  1. Define thunks with
    createAsyncThunk
    using a descriptive action type prefix:
    '<slice>/<operation>'
    .
  2. The payload creator receives two arguments: the single argument passed to
    dispatch(thunk(arg))
    , and
    thunkAPI
    which provides
    dispatch
    ,
    getState
    ,
    rejectWithValue
    , and
    signal
    .
  3. Always use
    rejectWithValue
    for known error shapes — it gives the reducer a typed payload instead of a serialized error.
  4. Handle all three lifecycle actions (
    pending
    ,
    fulfilled
    ,
    rejected
    ) in the slice's
    extraReducers
    .
  5. Track loading state with an enum (
    'idle' | 'loading' | 'succeeded' | 'failed'
    ) rather than separate booleans.
  6. Use
    condition
    to prevent duplicate fetches — return
    false
    to skip execution.
  7. Use
    thunkAPI.signal
    to support cancellation via
    AbortController
    .
// features/users/users.thunks.ts
import { createAsyncThunk } from '@reduxjs/toolkit';
import { RootState } from '../../store';

interface User {
  id: string;
  name: string;
  email: string;
}

export const fetchUsers = createAsyncThunk<
  User[], // Return type
  void, // Argument type
  { state: RootState; rejectValue: string }
>(
  'users/fetchAll',
  async (_, { rejectWithValue, signal }) => {
    const response = await fetch('/api/users', { signal });
    if (!response.ok) {
      return rejectWithValue(`Failed: ${response.status}`);
    }
    return response.json();
  },
  {
    condition: (_, { getState }) => {
      const { status } = getState().users;
      // Don't fetch if already loading or loaded
      return status === 'idle' || status === 'failed';
    },
  }
);
// features/users/users.slice.ts — extraReducers
extraReducers: (builder) => {
  builder
    .addCase(fetchUsers.pending, (state) => {
      state.status = 'loading';
      state.error = null;
    })
    .addCase(fetchUsers.fulfilled, (state, action) => {
      state.status = 'succeeded';
      state.items = action.payload;
    })
    .addCase(fetchUsers.rejected, (state, action) => {
      state.status = 'failed';
      state.error = action.payload ?? action.error.message ?? 'Unknown error';
    });
},

Details

Thunk lifecycle: Dispatching a thunk returns a promise. The thunk dispatches

pending
immediately, then
fulfilled
or
rejected
when the async work completes. The returned promise resolves with the action object in all cases (even rejection).

Unwrapping results: Use

.unwrap()
to get the payload directly or throw on rejection — useful in component handlers:

try {
  const users = await dispatch(fetchUsers()).unwrap();
  showSuccess(`Loaded ${users.length} users`);
} catch (err) {
  showError(err as string);
}

Cancellation: When the component unmounts, abort the thunk:

useEffect(() => {
  const promise = dispatch(fetchUsers());
  return () => promise.abort();
}, [dispatch]);

When to use RTK Query instead: If you have a REST/GraphQL API with standard CRUD, caching, polling, or optimistic updates, RTK Query handles all of this automatically. Use

createAsyncThunk
for non-standard async work (WebSocket messages, file uploads, multi-step workflows).

Source

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

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.