ReduxMiddleware
devguides.io / Redux

Middleware

Redux plugins are often made as middleware. Middleware are just functions. Here's a middleware that does nothing.

createStore(reducer, {}, applyMiddleware(logger))
function logger (store) {
  return function (dispatch) {
    return function (action) {
      return dispatch(action)
    }
  }
}
It make look complicated at first, but let's learn about this later!

With ES2015, you can shorten this to:

const logger = store => dispatch => action => {
  return dispatch(action)
}
See related documentation

Let's give extra powers to dispatch(). Next

Decorating dispatch

Middleware is most commonly used to make dispatch() do something else.

const logger = store => dispatch => action => {
  console.log('Dispatching:', action.type)
  console.log('Old state:', store.getState())
  var result = dispatch(action)
  console.log('New state:', store.getState())
  return result
}

In this example, we've made dispatch() log some messages before and after dispatching.

store.dispatch({ type: 'SAVE' })
Dispatching: SAVE
Old state: { ... }
New state: { ... }

Let's use this for something useful. Next

Side effects

A store's reducers should have no side effects. Middleware is often used for that instead. Let's write a middleware that performs AJAX requests using window.fetch().

const fetcher = store => dispatch => action => {
  const { type, next, url } = action
  if (type === 'FETCH') {
    dispatch({ type: `${next}_PENDING` })
    return fetch(url)
    .then(result => dispatch({ type: `${next}_SUCCESS`, result }))
    .then(error => dispatch({ type: `${next}_ERROR`, error }))
  } else {
    return dispatch(action)
  }
}
This function will replace dispatch(). It listens for FETCH actions.
store.dispatch({ type: 'FETCH', next: 'LOAD', url: '/data.json' })
When using the middleware, this dispatches LOAD_PENDING, LOAD_SUCCESS and LOAD_ERROR.

Let's improve your Redux experience with npm. Next

Common middleware

The npm ecosystem has a lot of useful Redux middleware. For instance, redux-thunk lets you dispatch functions.

store.dispatch((dispatch) => {  })

redux-multi lets you dispatch many actions in one action.

store.dispatch([
  { type: 'INCREMENT', payload: 2 },
  { type: 'INCREMENT', payload: 3 }
])

redux-logger shows you Redux actions as they happen.

action @ 13:11:00 FETCH (in 0.1ms)
action @ 13:11:01 LOAD_PENDING (in 0.1ms)
action @ 13:11:02 LOAD_SUCCESS (in 0.1ms)

Did you wonder why middleware looks like store => dispatch => action? Next

Middleware signature

You may have noticed that middleware functions look a bit confusing. It's a function, that returns a function, that returns a function.

const logger = store => dispatch => action => {
  return dispatch(action)
}

Forget about its strange look. Think of it as a single function.

function logger (store, dispatch, action) {  }

Having them as functions-that-return-functions makes for something interesting: it breaks the middleware into 3 steps that are applied separately.

const logger = function (store) {
  This function runs on createStore().
  It returns a decorator for dispatch().
  You can get store.getState() here.
  return function (dispatch) {
    This function runs on createStore() too.
    This returns a new dispatch() function to
    replace the old one.
    return function (action) {
      This runs every dispatch().
    }
  }
}

Most middleware will not need this, but the Redux docs has examples when this can be useful.

Let's recap what we've learned. Next

Recap

Middleware are Redux plugins you can attach to your store.

createStore(reducer, {}, applyMiddleware(logger, thunk))

They give dispatch() more powers.

store.dispatch({ type: 'SAVE' })     Normal
store.dispatch(fetch('/data.json'))  Promises
store.dispatch(() => {  })     Functions/thunks
store.dispatch()               ...and more

You can use middleware to write side effects to actions.

const middleware = store => dispatch => action => {
  if (action.type === 'FETCH') {
    doSomethingDifferent()
  }
  dispatch(action)
}

That's all for now! Back