fbpx Skip to content

Aquent | DEV6

Redux Middleware – A tool you aren’t using enough

Written by: Peter Wang

If you are using redux to manage the state of your application, you must be familiar with redux middleware, and you probably have used the most common `redux-logger` middleware for debugging purpose. However, if you dive deep into redux middleware, you will find a lot more use cases, such as crash reporting, error handling, etc. and make your redux application more concise and centralized.

What is a redux middleware:

Definition from redux official docs:

  • A “middleware” in redux is a higher-order function that composes a dispatch function to return a new dispatch function. It often turns async actions into synchronous actions.
  • It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.
Redux Middleware Diagram

As you can see from diagram above, a redux middleware is used to intercept dispatched actions before they make it to the reducer. This means that when you call dispatch on an action, the action goes through a (or many) middleware before hitting the reducer – if it even makes it that far, but more on that later.

You can apply multiple middleware to a redux store, which means that the action goes through all the middleware before making it to the reducer. The order of execution is actually the order in which you pass the middleware to the store. Also, at any point in a middleware, you can choose to stop forwarding the action, which will end the cycle.

Write middleware:

This is the signature of a redux middleware:

const middleware = store => next => action => {
   // do something here
}

The above function signature is just a curried form of a function that takes three arguments. In case you are wondering why currying, you can read here(10 min read).

Let’s have a closer look at this function.

`store`:  the Redux store instance that will be passed to your middleware.

`next`: a function that you need to call with an action when you want to continue the flow execution, which means passing the action to the next in line to either the following middleware or a reducer.

`action`: the action that was originally dispatched so that you can access it, apply logic based on the action, and eventually pass it on using next.

After you create this middleware function, you need to apply it to the redux store via `applyMiddleware` API from `redux`

import { createStore, combineReducers, applyMiddleware } from 'redux'

let todoApp = combineReducers(reducers)
let store = createStore(
   todoApp,
   // applyMiddleware() tells createStore() how to handle middleware 
   applyMiddleware(logger, crashReporter)
)

Here we have applied two middleware, logger and crashReporter. Now let’s see a couple of examples.

Logger Middleware:

const logger = store => next => action => {
    console.group(action.type)
    console.info('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    console.groupEnd()
    return result
}

In the logger middleware above, whenever there is an action being dispatched, we will start logging and grouping by the action type and after calling `next(action)`, which means passing the action to the next middleware, we are logging the current state of the whole store. Simple right?

CrashReporter Middleware:

/**
 * Sends crash reports as state is updated and listeners are notified.
 */
const crashReporter = store => next => action => {
    try {
        return next(action)
    } catch(err) {
        console.error('Caught an exception!', err)
        Raven.captureException(err, {
            extra: {
                action,
                state: store.getState()
            }
        })
        throw err
    }
}

This crash reporter middleware uses `Raven` as a third-party library to handle crashes when actions are being dispatched and send the causing action and the real-time state to Raven for analysis.

PromiseActions Middleware:

const vanillaPromise = store => next => action => {
    if (typeof action.then !== 'function') {
      return next(action)
    }

    return Promise.resolve(action).then(store.dispatch)
}

In this vanillaPromise middleware, we are checking if the action is a promise by checking the action’s thenproperty. If it’s not, we just pass it to next middleware or the reducer. If it is and if the action is resolved, we are dispatching the result and because we are returning the dispatch so the caller can handle the rejection.

Conclusion:

Redux middleware are an essential tool that offers tremendous flexibility for action manipulation and makes redux applications more maintainable. There are many more redux middleware out there. The followings are a few commonly used middleware:

Keep exploring more use cases and start building your own redux middleware.