MENU
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:
- dispatch(): the standard store.dispatch() method.
- getState(): the standard store.getState() method.
- getOriginalState(): the state before the reducers ran.
- extra: the object passed to the 'extra' property of createListenerMiddleware().
- unsubscribe(): prevents the listener from running in future.
- subscribe(): re-subscribes the listener if it was previously unsubscribed.
- cancelActiveListeners(): cancels all other running instances of this same listener except for the one that made this call.
- signal: An AbortSignal object whose 'aborted' property will be set to true if the listener execution is aborted or completed.
- take(predicate, timeoutMs): waits for another dispatch action and returns a promise that will resolve to (action, currentState, previousState) when the predicate returns true. If a timeout is provided and expires first, the promise resolves to null.
- condition(predicate, timeoutMs): waits for another dispatch action and returns a promise that will resolve to true if the predicate succeeds, and false if a timeout is provided and expires first.
- delay(timeoutMs): returns a promise that resolves after the timeout, or rejects if cancelled before the expiration.
- pause(promise): accepts any promise, and returns a promise that either resolves with the argument promise or rejects if cancelled before the resolution.
- fork(forkApi): launches a child task asynchronously.
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:
- 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 })
- 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: () => {}, })
- 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)