BlogsByAbhishek

Your go-to space for web development insights, tutorials, and tips

Master Redux Middleware from Scratch

A step-by-step guide to understanding and creating middleware in Redux. By the end, you'll know how to log actions, handle async behavior, and customize your Redux flow like a pro.

Introduction

Middleware in Redux acts like a filter. It lets you see and work with each action as it flows through Redux. Want to log every action? Or handle async API calls without messing up your components? That's where middleware shines!

In this guide, we'll create a custom middleware that logs every action and allows async actions (like fetching data from an API). Ready? Let's dive in!

Step 1: Setting Up Redux

First, make sure Redux is installed. Open your terminal and run:

npm install redux

This will add Redux to your project, allowing us to create a store, reducers, and (of course) our custom middleware.

Step 2: Writing Your First Middleware

Let's start simple. Middleware is just a function that can "see" each action, log it, and even modify it before passing it along. Here's our first version: a logger middleware that logs every action and the state before and after.

// middleware/loggerMiddleware.js

const loggerMiddleware = ({ dispatch, getState }) => (next) => (action) => {
  console.log("Dispatching action:", action);
  console.log("State before action:", getState());

  const result = next(action); // Pass the action to the next middleware or reducer

  console.log("State after action:", getState());
  return result;
};

export default loggerMiddleware;

What's Happening Here?

In this function:

  • We log the action type and the state before dispatching.
  • Then, we use next(action) to pass it to the next step in Redux.
  • Finally, we log the state again to see how it changed.

Step 3: Adding Middleware to the Redux Store

Now that we have a middleware function, we need to tell Redux to use it. Here's how you can set it up in your store:

// store.js

import { createStore, applyMiddleware } from 'redux';
import loggerMiddleware from './middleware/loggerMiddleware';

const initialState = {
  users: [],
};

function userReducer(state = initialState, action) {
  switch (action.type) {
    case 'ADD_USER':
      return { ...state, users: [...state.users, action.payload] };
    default:
      return state;
  }
}

const store = createStore(userReducer, applyMiddleware(loggerMiddleware));

export default store;

Now every time an action is dispatched, our loggerMiddleware will log it.

Step 4: Creating Async Middleware

Time for something cooler! Let's modify our middleware to handle async actions, like fetching user data. Here's a version that checks if an action is a function (common for async actions) and then executes it with dispatch and getState.

// middleware/asyncMiddleware.js

const asyncMiddleware = ({ dispatch, getState }) => (next) => (action) => {
  if (typeof action === 'function') {
    return action(dispatch, getState); // For async actions
  }
  return next(action); // For regular actions
};

export default asyncMiddleware;

Why This Matters

This async middleware allows us to dispatch functions that can perform async work, such as API requests. It checks if the action is a function, runs it if true, and otherwise passes it along.

Step 5: Combining Both Middlewares

Now that we have both logging and async functionality, let's use both middlewares together:

// store.js

import { createStore, applyMiddleware } from 'redux';
import loggerMiddleware from './middleware/loggerMiddleware';
import asyncMiddleware from './middleware/asyncMiddleware';

const store = createStore(userReducer, applyMiddleware(loggerMiddleware, asyncMiddleware));

export default store;

Step 6: Using an Async Action

Here's an example async action to fetch users. This action dispatches a request, waits 2 seconds to simulate an API call, and then dispatches success with the data.

// actions/userActions.js

export const fetchUsers = () => {
  return async (dispatch) => {
    dispatch({ type: 'FETCH_USERS_REQUEST' });

    try {
      const data = await new Promise((resolve) =>
        setTimeout(() => resolve([{ id: 1, name: 'Alice' }]), 2000)
      );

      dispatch({ type: 'FETCH_USERS_SUCCESS', payload: data });
    } catch (error) {
      dispatch({ type: 'FETCH_USERS_FAILURE', error: 'Failed to fetch users' });
    }
  };
};

What Happens in the Console

When fetchUsers is called, the middleware will log the FETCH_USERS_REQUEST action, wait 2 seconds, and then log the FETCH_USERS_SUCCESS action with the data. Try it out!

Conclusion

By creating middleware, you've taken your Redux knowledge to the next level! Now you can log, handle async tasks, and control how actions flow through Redux. Use this foundation to build powerful, flexible apps. Happy coding!