Build your own Redux middleware

A quick-start guide to Redux’s underused feature!

Abinav Seelan
campvanilla
Published in
5 min readDec 8, 2017

--

There is no denying the impact that Redux has had on the React (and even Angular) landscape. Over the years, it has cemented its position as one of the most popular state management libraries.

That being said, an aspect of Redux that is sometimes underused is its support for middleware.

Now, before we jump into what a middleware is and how we can write one for redux, let’s have a quick refresher! 🛀🏻

A quick overview of Redux

Redux has an opinionated set of principles, three in fact, that serve as the foundation for the way state is managed by it.

  • Single Source of Truth: Redux has a single place where state is stored, called the Store. The store is essentially just a big javascript object. Views can subscribe to the entire store or subsets of this store.
  • State is read-only: Views cannot directly modify the store. Instead, they have to dispatch an action which defines how the store must change.
  • Modifications to the state occur through pure functions: A proposed change to the store, defined by the action that is dispatched, is handled by a pure function called the reducer.
    The reducer takes the current state of the store and the action that has been dispatched and returns a new state for the store.

To paint a picture of how all these entities interact 👨‍🎨

Redux Middleware

So where exactly does a redux middleware fit in ☝️?

A Redux middleware sits between the action that has been dispatched and the reducer.

And how does a middleware look in code?

const awesomeMiddleware = function(state) {
return function(next) {
return function(action) {
// We can use values from the store via state
// We have access to action that's currently being dispatched
// Once you're done, call next
next(action);
}
}
}

That…isn’t the prettiest thing to look at. We can ES6-it up and make it look a little bit tidier!

const awesomeMiddleware = state => next => action => {
// Since we have access to state, we can use values from the store
// We also have access to action that's currently being dispatched
// Once you're done, call next
next(action);
}

The middleware is a curried function that brings into its scope

  • the store’s state
  • the action being dispatched
  • the next function, which is used to pass along the action being dispatched to the next middleware, or to the reducer if this is the last middleware! (If you don’t call next() the action will not be passed along and it will not reach the reducer. So be wary. )

(PS: If you’re curious why the middleware is a triple curried function and not just awesomeMiddleware(state, next, action), the redux documentation has a detailed explanation as to why it was done this way. You can check that out here.) 😄

Those who have used ExpressJS would find this rather familiar. I’m not sure if it was based on it or just a happy coincidence, but a redux middleware looks and functions the same way an express middleware does in an express app! 🤖

// An express middlewareapp.use((request, response, next) => {
// do something with request and response
// call next once you're done.
next(request, response);
});

Building a simple middleware

Let’s build a simple middleware that will log to the console the state of the application and the action being dispatched if:

  • the action has a payload with error: <some error message> (where payload is the data being sent along with the action)
  • the action type contains the substring _ERROR in it

For example, action(s) that this middleware would handle will look like this

{
type: 'TODO_ERROR',
}
or {
type: 'ADD_TODO',
payload: {
error: 'Oops. Something happened'
}
}

Ok. So we know how the structure of the middleware looks.

const errorLogger = state => next => action {  // do something}

Now, let’s check for an action that has a payload with a defined error key.

const errorLogger = state => next => action {
if (action.payload.error) {
console.log("An error occurred");
}

}

Let’s also check if the action.type contains the substring _ERROR.

const errorLogger = state => next => action {
if (action.payload.error || (/_ERROR/i).test(action.type)) {
console.log("An error occurred");
}
}

Here, we’re just logging this to the console. But this can be powerful in production environments to collect error logs. Those logs need to be descriptive, so let’s also print out the current state of the application!

const errorLogger = state => next => action {
if (action.payload.error || (/_ERROR/i).test(action.type)) {
console.log("An error occurred");
console.log(action); // log the action being dispatched
console.log(state.getState()); // log the current store

}
}

And we’re almost done!

The last thing we need to do is forward the action to the reducer or the next middleware.

const errorLogger = state => next => action {
if (action.payload.error || (/[A-Z]*_ERROR/i).test(action.type)) {
console.log("An error occurred");
console.log(action); // log the action being dispatched
console.log(state.getState()); // log the current store
}
next(action);
}

And that’s it. We’ve built our first redux middleware! 🚀

Now all that’s left is to use this middleware in an actual application. 😄

import { createStore, applyMiddleware } from 'redux';
import reducer from 'path/to/reducer';
const errorLogger = state => next => action {
if (action.payload.error || (/[A-Z]*_ERROR/i).test(action.type)) {
console.log("An error occurred");
console.log(action); // log the action being dispatched
console.log(state.getState()); // log the current store
}
next(action);
}
const middleware = applyMiddleware(errorLogger);const store = createStore(reducer, middleware);

~ Fin ~

Stuck somewhere, need more help, or just want to say hi? Send me a Direct Question on Hashnode or hit me up on Twitter. You can also find me on Github. 🙃

--

--