Creating Our Own Redux Middleware
Tags: webdev, javascript, react, redux
Published: 2021-01-25 01:48:27

**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" });
```

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));
```

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);
  })();
};
```

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" });
```

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" });
```

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.