paint-brush
Redux Middleware Lifecycleby@zhirzh
6,302 reads
6,302 reads

Redux Middleware Lifecycle

by Shirsh ZibbuAugust 3rd, 2018
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In my article, <a href="https://medium.com/@zhirzh/redux-lifecycle-8e93908af03a" target="_blank">Redux Lifecycle</a>, I highlighted the simplicity of a <a href="https://hackernoon.com/tagged/redux" target="_blank">redux</a> reducer and the complexity that arises from the interaction between these reducers. And as we saw there, the sole purpose of a reducer is <strong>state manipulation</strong>.

Coins Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Redux Middleware Lifecycle
Shirsh Zibbu HackerNoon profile picture

Impure and asynchronous; but still redux

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?

Middleware

Borrowing words from redux.js.org, a middleware:

  • is not a fundamental part of the Redux architecture but is considered useful enough to be supported right in the core
  • is the suggested way to extend Redux with custom functionality and can be composed in a chain
  • provides a third-party extension point between dispatching an action, and the moment it reaches the reducer
  • has the same plural form (i.e. middlewares is not a word)
  • almost always looks like:



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

Middleware Input

A middleware takes three arguments:

mwApi

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.

next

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

action

An action object injected into the redux lifecycle by:

  • store’s own dispatch method (after passing all middleware)
  • a middleware directly calling mwApi.dispatch

Once the action reaches the dispatch() after passing the final middleware in the pipeline, it is dispatched to the reducers.

Middleware Output

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.

Middleware Sideways

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)}}

Final Thoughts

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.