In my article, Redux Lifecycle, I highlighted the simplicity of a redux reducer and the complexity that arises from the interaction between these reducers. And as we saw there, the sole purpose of a reducer is state manipulation.
What about actions? Who performs action manipulation?
Borrowing words from redux.js.org, a middleware:
const middleware = (mwApi) => (next) => (action) => {// do something};
This is just the curried form of a function that takes in 3 arguments and so I will write it in its original form:
function middleware(mwApi, next, action) {// do something}
Note: curry? confused? you might want to have a look at this 10 min read
A middleware takes three arguments:
An object with two methods — getState() and dispatch() — that is sometimes written as the ‘store’ in the middleware definition when it’s not. Nonetheless, we can use getState() to get the complete state tree from the store and call dispatch() with some action to ‘inject’ actions in the redux lifecycle.
As stated above, middleware can be composed in a chain. This composition is done by passing middleware functions to applyMiddleware and connecting the returned store enhancer to the redux store.
The provided middleware create a pipeline of callbacks where the next middleware in the chain becomes the next argument of a middleware.
The final middleware in the pipeline receives the store’s dispatch. This way, there is a single standard way to extend dispatch in the ecosystem.
In other words:
function mw1(mwApi, next, action) {...}function mw2(mwApi, next, action) {...}...function mw_N_(mwApi, next, action) {...}
const store = createStore(rootReducer,applyMiddleware(mw1, mw2, ..., mw_N_))
//> mw1.next === mw2//> mw2.next === mw3//> ...//> mw_N_.next === store.dispatch
An action object injected into the redux lifecycle by:
Once the action reaches the dispatch() after passing the final middleware in the pipeline, it is dispatched to the reducers.
Middleware don’t return anything since they form a callback pipeline and their return values are completely ignored. Just FYI, nobody’s stopping you from returning a value of your liking from a middleware.
Sometimes, we can choose not to call next() in certain conditions to stop the flow of action down the pipeline and into the reducers. This can be thought of as dumping an action into a “metaphorical” sink.
function mw(mwApi, next, action) {// just don't call next(action)}
A middleware can also modify the current action before passing it on or pass some other action in its place.
function mw(mwApi, next, action) {if (action.type === FOO) {action.payload.push(foo)}
if (action.type === BAR) {action = barAction()}
next(action)}
Even crazier, with access to the dispatch() method, a middleware can decide to “translate” an action into a series of other actions.
function mw(mwApi, next, action) {if (action.type === FOO) {mwApi.dispatch(firstFooAction())mwApi.dispatch(secondFooAction())mwApi.dispatch(notFooAction())} else {next(action)}}
In redux, middleware are a force to reckon with. They offer us tremendous flexibility for action manipulation but they also carry heavy implicit risks. One misplaced next() call is all it takes to shutdown your entire app.
We can easily abstract away complex behaviour in our apps, like:
Start exploring the different middleware by visiting the Ecosystem page on the official redux website.