Skip to content
Back to skills

Redux Listener Middleware

ASecurity

> React to dispatched actions and state changes with createListenerMiddleware for structured side effects

  • 20 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 11, 2026
developmenttypescriptreactapi

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 11, 2026

npx -y skills add Intense-Visions/harness-engineering --skill redux-listener-middleware --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Redux Listener Middleware?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Redux Listener Middleware
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/intense-visions-redux-listener-middleware/badge)](https://www.skillsdirectory.com/skills/intense-visions-redux-listener-middleware)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# 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

1. Create the listener middleware once with `createListenerMiddleware()`. Add it to the store via the `middleware` callback.
2. Use `startListening` to register listeners. Match actions with `actionCreator`, `type`, `matcher`, or `predicate`.
3. The `effect` callback receives the matched `action` and a `listenerApi` with `dispatch`, `getState`, `getOriginalState`, `condition`, `take`, `delay`, and more.
4. Use `listenerApi.condition()` to wait for a future state condition before continuing. Use `listenerApi.take()` to wait for a specific action.
5. Use `listenerApi.cancelActiveListeners()` at the start of the effect to debounce — cancels previous runs of the same listener.
6. Return or call `listenerApi.unsubscribe()` to remove the listener dynamically.

```typescript
// 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));
  },
});
```

```typescript
// store/index.ts
import { listenerMiddleware } from './listenerMiddleware';

export const store = configureStore({
  reducer: {
    /* ... */
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().prepend(listenerMiddleware.middleware),
});
```

## Details

**Matching strategies:**

- `actionCreator` — exact action creator match (best TypeScript inference)
- `type` — string match on `action.type`
- `matcher` — any RTK matcher (`isAnyOf`, `isAllOf`, `isRejected`)
- `predicate` — `(action, currentState, previousState) => boolean` for state-based conditions

**condition and take:** These let you write multi-step async workflows:

```typescript
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

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.

Files in this skill

  • SKILL.md4.5 KB
  • skill.yaml748 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.