In a previous article I wrote about how Redux stores provide a dispatch
function to “send” action objects to the reducer in order to update the store’s state.
But what if we want to do something before or after each and every dispatch
such as logging, centralised error handling or allowing dispatch to handle action creators which return Promises?
The solution to this is called Redux Middleware.
What is Middleware?
According to the official Redux documentation:
Middleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.
https://redux.js.org/advanced/middleware
To do this middleware effectively wraps the store.dispatch
function with custom implementations which provide additional functionality.
You can think of this as (roughly) akin to reassigning the store.dispatch
to another function which does something new and then calls the original dispatch
function.
In the example below, the original store.dispatch
is overwritten and replaced (monkey-patched) with an enhanced dispatch function logAllActionTypes
which wraps the original dispatch
function to provide additional logging functionality.
/**
* DO NOT COPY - example code only
*/
const logAllActionTypes = (store) => {
// Reference to original dispatch
const originalDispatch = store.dispatch;
// Return "new" dispatch function with same signature.
return (action) => {
// Add additional functionality
console.log(action.type);
// Call original dispatch with the action.
const returnValue = originalDispatch(action);
return returnValue;
}
}
const store = createStore();
// Enhance original store
store.dispatch = logAllActionTypes(store);
A better solution with applyMiddleware
Monkey patching store.dispatch
as we do above is not a great approach. As a result, Redux provides a utility called applyMiddleware
that solves the problem of enhancing dispatch in a more elegant fashion.
It does this by allowing you to define a series/chain of middleware functions. Each function is curried in order to provide access to store
and the “next” wrapped dispatch function in the middleware chain.
Here’s our example above rewritten as a middleware function compatible with applyMiddleware
const logAllActionTypes = store => next => action => {
// Add additional functionality
console.log(action.type);
// Call next dispatch with the action.
const returnValue = next(action);
return returnValue;
}
createStore( someReducer, [], applyMiddleware( logAllActionTypes ) );
Note that it is the “inner” function (the one that accepts action
as a parameter) which is called when an action is dispatched. However, this function has access to the store
(for convenience) and is able to call the next
wrapped dispatch function in the chain in order to ensure that middlewares are fully composable.
Understanding Redux’s applyMiddleware
To understand how this works consider the following lines from Redux’s applyMiddleware
implementation.
First a subset of the store’s API is referenced. This reduced API is what will become the (first) store
argument of all the curried middleware functions:
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
Next a “chain” of middleware is created by applying middlewareAPI
to all the middleware functions:
const chain = middlewares.map(middleware => middleware(middlewareAPI));
Finally, a new dispatch
is created by composing the result of calling all the middleware functions from right to left. The “rightmost” function receives the original store.dispatch
, with each function applying new functionality until a new enhanced dispatch is returned.
dispatch = compose( ...chain )( store.dispatch );
Each middleware requires no knowledge of what comes before or after it in the chain with the exception that it must call the next
function.
It is this composability that is the key feature of middleware and is what makes it incredibly flexible.
Further learning
For more information on understanding and using Middleware within Redux please see:
- “The Middleware Chain” video from Dan Abramov’s Egghead.io Redux course.
- The official Redux documentation on Middleware.
- The official Redux API reference for
applyMiddleware
.