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.mdsource 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
- Define thunks with
using a descriptive action type prefix:createAsyncThunk
.'<slice>/<operation>' - The payload creator receives two arguments: the single argument passed to
, anddispatch(thunk(arg))
which providesthunkAPI
,dispatch
,getState
, andrejectWithValue
.signal - Always use
for known error shapes — it gives the reducer a typed payload instead of a serialized error.rejectWithValue - Handle all three lifecycle actions (
,pending
,fulfilled
) in the slice'srejected
.extraReducers - Track loading state with an enum (
) rather than separate booleans.'idle' | 'loading' | 'succeeded' | 'failed' - Use
to prevent duplicate fetches — returncondition
to skip execution.false - Use
to support cancellation viathunkAPI.signal
.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
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- 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.