Redux api — applyMiddleware

Tam Nguyen
5 min readApr 3, 2018

--

Redux actually is not new today. It had created since 2015 and I’ve just worked with it a few months ago. I write this article with an attitude of a person that has just explored something excited. And I believe that: digging deeply to the library’s source code is really cool.

To read efficiently, I assumes that you guy understand Redux concept and wish to know how Redux works with middlewares and get the ability to create your own middleware also. If you are newbies, It’s recommended to you to read Redux introduction first and clone some boilerplate to gain it’s basic knowledge.

Middleware story

applyMiddleware api, in literal meaning, is aimed to override Redux primitive dispatch function by composing a chain of middlewares and Redux dispatch function together.

The reason leads to this overriding is that Redux dispatch function only retrieves an plain object action as input and response that very action after executed.

Then the state will be updated immediately once dispatch was called. And that function is the only way to trigger reducers for state changes. So what’s happen if we want to log before and after every dispatch call? We may repeat the following ugly code snippet:

console.log('Before call')
dispatch({type: 'FETCH_USERS'})
console.log('After call')

More difficulty, we wanna log the previous state and next state in every action dispatched. And this code snippet show the disability.

Or we want to create an action composing multiple tasks inside it and reuse after that like:

dispatch({type: 'FETCH_USER'})
FetchUserApi.call().then(users => dispatch({type: 'RETRIEVE_USERS', payload: users}))

That’s why we need a middleware here to intercept dispatching process.

The well-known middlewares that we are usually heard to overcome these disadvantages are redux-logger and redux-thunk.

Here are very simple middlewares I wrote to illustrate logger middleware.

const createLoggerMiddleware = () => {
const loggerMiddlewareApiInjector = ({ dispatch, getState }) => {

/**
*
@param next Action handler of next middleware
*
@returns {loggerActionHandler}
*/
const nextActionHandlerInjector = next => {

const loggerActionHandler = (action) => { // Action handle


let previousState = getState();
next(action)
let nextState = getState()

console.group('Log action:', action.type)
console.log('Previous state', {...previousState})
console.log('Next state', {...nextState})
console.groupEnd();
}

return loggerActionHandler;
}

return nextActionHandlerInjector
}

return loggerMiddlewareApiInjector;
}

export default createLoggerMiddleware();

And redux-thunk source code is simple enough to bring here for illustration.
I based on Redux-Thunk and add some comments, function names to make it clearer.

function createThunkMiddleware(extraArgument) {
const thunkMiddlewareApiInjector = ({ dispatch, getState }) => {

/**
*
@param next next Action handler of next middleware
*
@returns {thunkActionHandler}
*/
const nextActionHandlerInjector = next => {

const thunkActionHandler = action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}

return next(action);
};

return thunkActionHandler;
}

return nextActionHandlerInjector;
}

return thunkMiddlewareApiInjector;
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

You should clone this demo app and try it out. You will see the result in browser development tool.

Look through the above code, we see 3 functions with those paramters are: {dispatch, getState}, next, action. How does it work, when it is called and what does it mean. Those are our questions need to be answered. So, proceed to get the answers.

Anatomize applyMiddleware method.

I did some comment on Redux source code for explanation.

export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {

// Recursive call createStore to get return value.
const store = createStore(reducer, preloadedState, enhancer)
// store = {
// dispatch,
// getState,
// ...
// }

let dispatch = store.dispatch
let
chain = []

// Declare middleware APIs to inject to middleware.
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}


// Sound so familiar, right. If you see my logger or redux-thunk code, parameters of every middleware is very middlewareAPI
// Now, loop through and call each middlewares and to get a chain of Next-action-handler injectors. What is the Next-action-handler injectors? I am about to explain. For shortly, we agree Next-action-handler injector named NAHInjector

// We have
// middlewares = [thunkMiddlewareApiInjector, loggerMiddlewareApiInjector]
chain = middlewares.map(middleware => middleware(middlewareAPI))
// => chain = [thunkNAHInjector, loggerNAHInjector]


let composedChain = compose(...chain)
// => composedChain = (args) => thunkNAHInjector(loggerNAHInjector(args))
// => composedChain = (args) => thunkNAHInjector(loggerActionHandler)
// => composedChain = (args) => thunkActionHandler // Thunk-middleware action handler

// You see that. "loggerActionHandler" is the parameter of "thunkNAHInjector". So to write a middleware, we always have a NAHInjector to retrieve the action handler of the right-next middleware.

dispatch = composedChain(store.dispatch)
// So. the next action handler that was injected to loggerNAHInjector (last middleware in chain) was primitive dispatch function.
// Or we can say, the last middleware in the chain of middlewares will receive primitive dispatch function as the next action handler.

// Now we have
// dispatch = composedChain(store.dispatch)
// => dispatch = thunkActionHandler

return {
...store,
dispatch
}
}
}

Now, we all understood about mechanism of applyMiddleware. Let’s check with the demo app

TL;DR

applyMiddleware api, in literal meaning, is aimed to override Redux primitive dispatch function by composing a chain of middlewares and Redux dispatch function together.

After done execution of applyMiddleware function, store.dispatch now is the action handler of the first middleware of the chain. Then if we call “next(action)”, that means we call to the action handler of the next middleware, until the end of middleware chain. And at the last middleware, “next” will be primitive dispatch function of Redux.

Dispatch process in a picture:

To write a new middleware, we must comply these rules:

  • Must have middleware API injector: middlewareApiInjector({dispatch, getState}) => {}
    — dispatch: action handler of the first middleware in chain (after applyMiddleware had done his jobs).
    — getState: return current state of redux
    The use of these parameters is optional
  • Must have next action handler injector which is return value of middlewareApiInjector function: nextActionHandlerInjector(next)=> {}
    — next: action handler of the next middleware in middleware chain. If your middleware is the last one in chain, next is primitive dispatch function
  • Must have action handler as return value of nextActionHandlerInjector: actionHander(action) => {}.
    In this function body, call “next(action)” if you wanna use primitive dispatch.

DO NOT call “dispatch(action)” in actionHander because dispatch is not the primitive dispatch, that is action handler of the first middleware of middleware chain.

Thanks for reading! :)

--

--