Creating Our Own Redux Middleware

John Au-Yeung - Jan 25 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it also as a popular state management solution for React apps when combined with React-Redux.

In this article, we’ll look at how to create our own Redux middleware.

Middleware

Middleware is some code that we can put between dispatching an action and the moment that the action reaches the reducer.

To do this in a clean way, we define a function that takes the store as a parameter, which returns a function that takes the next function, which is the same as the dispatch function, which then returns a function that takes an action parameter and returns next(action) .

We can do that as follows:

import { createStore, applyMiddleware } from "redux";

function countReducer(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

const logger = store => next => action => {
  console.log("dispatching", action);
  let result = next(action);
  console.log("next state", store.getState());
  return result;
};

let store = createStore(countReducer, applyMiddleware(logger));
store.subscribe(() => store.getState());
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "DECREMENT" });
Enter fullscreen mode Exit fullscreen mode

In the code above, we have the logger middleware, which is a function that takes the store parameter which holds the Redux store itself, which then returns a function that takes the next function, which the same as store.dispatch . This function then returns a function that takes the action parameter, which is our action object which we called dispatch with.

In the function that takes action , first, we log action, then we call next with action , then we take the returned result and assigned it to result .

Then we log store.getState() , and return result .

To use the logger middleware we just defined, we write:

let store = createStore(countReducer, applyMiddleware(logger));
Enter fullscreen mode Exit fullscreen mode

Then when we run dispatch we’ll see the console.log output from the logger.

Returning Non-Plain Objects with Middleware

We don’t have to return plain objects with middleware.

For example, we can also return a promise as follows:

const promiseMiddleware = store => next => action => {
  if (!(action instanceof Promise)) {
    return next(action);
  }
  return (async () => {
    const resolvedAction = await Promise.resolve(action);
    store.dispatch(resolvedAction);
  })();
};
Enter fullscreen mode Exit fullscreen mode

In the code above, we returned a promise, which isn’t a plain object.

All it does is that if we pass in a promise into dispatch , it’ll resolve the promise and then call store.dispatch with the resolved action.

We can then call the applyMiddleware function when the createStore function is called.

Note that we called our async function, we don’t just return it. We have to call store.dispatch so that the values from our actions will be set in the store.

Defining and Using Multiple Middlewares

We can create and apply multiple middlewares when we call applyMiddleware . To do this, we just pass it all the middlewares for applyMiddleware .

For example, if we want to apply all the middlewares in the examples we have above, we can write:

import { createStore, applyMiddleware } from "redux";

function countReducer(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

const logger = store => next => action => {
  console.log("dispatching", action);
  let result = next(action);
  console.log("next state", store.getState());
  return result;
};

const promiseMiddleware = store => next => action => {
  if (!(action instanceof Promise)) {
    return next(action);
  }
  return (async () => {
    const resolvedAction = await Promise.resolve(action);
    store.dispatch(resolvedAction);
  })();
};

let store = createStore(
  countReducer,
  applyMiddleware(promiseMiddleware, logger)
);
store.subscribe(() => store.getState());
store.dispatch(Promise.resolve({ type: "INCREMENT" }));
store.dispatch({ type: "DECREMENT" });
Enter fullscreen mode Exit fullscreen mode

Since we have our promiseMiddleware , we can now pass in promises to dispatch and set the value in the store.

Therefore, we can write:

store.dispatch(Promise.resolve({ type: "INCREMENT" }));
store.dispatch({ type: "DECREMENT" });
Enter fullscreen mode Exit fullscreen mode

and we won’t get an error.

With the logger middleware, we get console.log output.

Conclusion

We can define middleware to do more than what we can do with Redux itself.

To do this, we define a function that takes the store as a parameter, which returns a function that takes the next function, which is the same as the dispatch function, which then returns a function that takes an action parameter and returns next(action) .

The function has to call dispatch so that the action object can be propagated to the reducer.

We can chain middlewares with the applyMiddleware function, which takes one or more middlewares.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .