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-listener-middleware" ~/.claude/skills/intense-visions-harness-engineering-redux-listener-middleware && rm -rf "$T"
manifest:
agents/skills/claude-code/redux-listener-middleware/SKILL.mdsource content
Redux Listener Middleware
React to dispatched actions and state changes with createListenerMiddleware for structured side effects
When to Use
- Running side effects in response to specific actions (analytics, logging, sync)
- Implementing "when X happens, do Y" reactive logic that does not fit in a reducer
- Replacing redux-saga or redux-observable with a simpler built-in alternative
- Coordinating cross-slice logic (when slice A changes, update slice B)
Instructions
- Create the listener middleware once with
. Add it to the store via thecreateListenerMiddleware()
callback.middleware - Use
to register listeners. Match actions withstartListening
,actionCreator
,type
, ormatcher
.predicate - The
callback receives the matchedeffect
and aaction
withlistenerApi
,dispatch
,getState
,getOriginalState
,condition
,take
, and more.delay - Use
to wait for a future state condition before continuing. UselistenerApi.condition()
to wait for a specific action.listenerApi.take() - Use
at the start of the effect to debounce — cancels previous runs of the same listener.listenerApi.cancelActiveListeners() - Return or call
to remove the listener dynamically.listenerApi.unsubscribe()
// store/listenerMiddleware.ts import { createListenerMiddleware, isAnyOf } from '@reduxjs/toolkit'; import { addTodo, toggleTodo } from '../features/todos/todos.slice'; import { RootState } from './index'; export const listenerMiddleware = createListenerMiddleware(); // Sync todos to localStorage whenever they change listenerMiddleware.startListening({ matcher: isAnyOf(addTodo, toggleTodo), effect: async (action, listenerApi) => { const state = listenerApi.getState() as RootState; localStorage.setItem('todos', JSON.stringify(state.todos.items)); }, }); // Debounced search — cancel previous runs listenerMiddleware.startListening({ actionCreator: setSearchQuery, effect: async (action, listenerApi) => { // Cancel any in-progress instances of this listener listenerApi.cancelActiveListeners(); // Debounce 300ms await listenerApi.delay(300); // If we get here, no new setSearchQuery was dispatched listenerApi.dispatch(fetchSearchResults(action.payload)); }, });
// store/index.ts import { listenerMiddleware } from './listenerMiddleware'; export const store = configureStore({ reducer: { /* ... */ }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(listenerMiddleware.middleware), });
Details
Matching strategies:
— exact action creator match (best TypeScript inference)actionCreator
— string match ontypeaction.type
— any RTK matcher (matcher
,isAnyOf
,isAllOf
)isRejected
—predicate
for state-based conditions(action, currentState, previousState) => boolean
condition and take: These let you write multi-step async workflows:
listenerMiddleware.startListening({ actionCreator: startCheckout, effect: async (action, listenerApi) => { // Wait for payment to complete (or timeout after 60s) const [paymentAction] = await listenerApi.take(paymentCompleted.match, 60_000); if (paymentAction) { listenerApi.dispatch(finalizeOrder()); } else { listenerApi.dispatch(checkoutTimedOut()); } }, });
Comparison with alternatives:
- Thunks: Best for single async operations dispatched from components. Listeners are best for reactive "when X happens do Y" patterns.
- Sagas: Listeners cover most saga use cases without generators. Use sagas only if you need advanced concurrency patterns (races, forks, channels).
- Observables: Listeners handle serial async workflows well. Use RxJS only if you need complex stream composition.
Prepend, not concat: Use
.prepend(listenerMiddleware.middleware) so listeners run before other middleware.
Source
https://redux-toolkit.js.org/api/createListenerMiddleware
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.