SkillByAIOpen interactive version →

Lesson 9 / 25

Listener Middleware and Side Effects

React to actions without putting effects in reducers.

Reactive side effects

createListenerMiddleware lets you run effect callbacks in response to actions or state changes: analytics, saving drafts, debounced search, or starting work after another thunk finishes. Register listeners with startListening({ actionCreator | matcher | predicate, effect }). The effect receives the action and a listenerApi with getState, dispatch, delay, condition, take and cancelActiveListeners for debounce-style logic. It covers many use cases that previously needed redux-saga or redux-observable, with a smaller API.

Debounced autosave

listener.ts and store wiring.

import { createListenerMiddleware } from '@reduxjs/toolkit';

export const listener = createListenerMiddleware();

listener.startListening({
  actionCreator: draftEdited,
  effect: async (action, listenerApi) => {
    listenerApi.cancelActiveListeners(); // cancel earlier pending runs (debounce)
    await listenerApi.delay(800);        // throws if this run gets cancelled
    const draft = (listenerApi.getState() as RootState).editor.draft;
    await fetch('/api/drafts', { method: 'PUT', body: JSON.stringify(draft) });
    listenerApi.dispatch(draftSaved());
  },
});

// store.ts
// middleware: (gDM) => gDM().prepend(listener.middleware)

Keep reducers pure

No fetch calls, timers, random IDs or Date.now() inside reducers. Generate IDs in a prepare callback or in the dispatching code, and put effects in thunks or listeners.

Quick check: Where should an autosave network call triggered by an action live in RTK?

  • Inside the selector
  • Inside the slice reducer
  • In a listener middleware effect or a thunk
  • In initialState
Answer

In a listener middleware effect or a thunk — Reducers must stay pure; effects go in middleware.