Mark Pollmann's blog

Understanding Redux Middleware by understanding redux-thunk

There has always been the notion that Redux middleware is some kind of black magic but the idea behind it is actually quite simple and elegant.

The Main Idea

Before reaching reducers actions can be manipulated with global functions called middleware. They can do whatever they want to the actions: log them to the console, change properties or stop some of them entirely. If you already know ExpressJS middleware you know Redux middleware, it’s the same idea. Every action flows through the registered middleware functions, one after the other and in order of registered middleware.

For example, if this is the code of our store:

const store = createStore(Reducer, applyMiddleware(thunk, myCustomMiddleware))

then actions flow through thunk first, then through myCustomMiddleware and then get processed by the reducers.

Some Examples of Custom Middleware

Let’s take a very simple middleware. console.loging the action and then letting the action pass to the next middleware.

function loggerMiddleware() {
  return ({ dispatch, getState }) => next => action => {
    console.log(action)
    return next(action)
  }
}

Now let’s write one that stops all actions from further processing:

function stopperMiddleware() {
  return ({ dispatch, getState }) => next => action => {
    console.log("You shall not pass!")
  }
}

As you can see, if the call to next is missing the flow to the next middleware (and later the reducers) stops.

Now let’s write one that, no matter what the action is, overwrites it with a another, fixed type of action:

function loggerMiddleware() {
  return ({ dispatch, getState }) => next => action => {
    return next({ type: "trololo" })
  }
}

I hope you get the idea.

Redux Thunk

Now that we understand what’s going on, let’s take a look at the redux-thunk library code.

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === "function") {
      return action(dispatch, getState, extraArgument)
    }

    return next(action)
  }
}

const thunk = createThunkMiddleware()
thunk.withExtraArgument = createThunkMiddleware

export default thunk

Yup, that’s all the code there is.

Let’s see, what does if (typeof action === 'function') mean, actions are always plain objects, aren’t they? The ones that reach reducers yes, and that’s the whole trick. Redux-thunk intercepts these cheating function-actions (that are created by passing dispatch() a function instead of an object), injects the three arguments and runs them. Actions that pass redux-thunk are (or should be) plain objects again. If not, somebody messed up and redux will complain.

Once you wrap your head around the functional programming style of returning functions from other functions and passing them around it will make more sense.

Thanks for reading!