configureStore ... Listener Middleware

You can define 'listener' entries that are fired in response to Redux store updates.

In contrast to listeners specified by store.subscribe() which are fired on all dispatch actions, listeners supplied to listenerMiddleware.startListening() are fired on certain actions only.

Use one of the four ways to specify when a listener will be fired -- 'type', 'actionCreator', 'matcher', or 'predicate'. Notice when the listeners are fired relative to the updates and rendering.

You can dynamically add and remove listeners at runtime by dispatching special "add" and "remove" actions.

To do so, first import addListener(), removeListener(), clearAllListeners() from "@reduxjs/toolkit".

const unsubscribe = store.dispatch(addListener({ predicate, effect })); // same arguments as startListening() above const wasRemoved = store.dispatch(removeListener({ predicate, effect, cancelActive: true })); // same arguments as stopListening() above store.dispatch(clearAllListeners());


listenerApi, the second argument of the effect callback, has the following members:

Store Interaction Subscription Management 'Cancellable' Conditional Workflow Child Forking
As you use the listenerApi functions 'condition', 'take', 'pause', and 'delay', you may cancel the listener when it is running, with unsubscribe({ cancelActive: true }) for instance.
An example of this might be a listener that forks a child task containing an infinite loop that listens for events from a server. The parent then uses listenerApi.condition() to wait for a 'stop' action, and cancels the child task.

It's best to create the listener middleware in a separate file (eg. app/listenerMiddleware.js) rather than in the same file as the store. You can:

  1. import effect callbacks from slice files into the middleware file, and add the listeners
    // app/listenerMiddleware.js
    import { action1, listener1 } from '../features/feature1/feature1Slice'
    import { action2, listener2 } from '../features/feature2/feature1Slice'
    
    listenerMiddleware.startListening({ actionCreator: action1, effect: listener1 })
    listenerMiddleware.startListening({ actionCreator: action2, effect: listener2 })
    
  2. have the slice files import the middleware and directly add their listeners
    import { listenerMiddleware } from '../../app/listenerMiddleware'
    
    const feature1Slice = createSlice(/* */)
    const { action1 } = feature1Slice.actions
    
    export default feature1Slice.reducer
    
    listenerMiddleware.startListening({
      actionCreator: action1,
      effect: () => {},
    })
    
  3. create a setup function in the slice, but let the listener file call that on startup
    import type { AppStartListening } from '../../app/listenerMiddleware'
    
    const feature1Slice = createSlice(/* */)
    const { action1 } = feature1Slice.actions
    
    export default feature1Slice.reducer
    
    export const addFeature1Listeners = (startListening: AppStartListening) => {
      startListening({
        actionCreator: action1,
        effect: () => {},
      })
    }
    
    // app/listenerMiddleware.js
    import { addFeature1Listeners } from '../features/feature1/feature1Slice'
    
    addFeature1Listeners(listenerMiddleware.startListening)