DEV Community

bilal khan
bilal khan

Posted on

Advanced Redux: Middleware and Custom Logic Integration

In modern applications, Redux middleware allows you to handle complex logic that doesn’t fit neatly into reducers or actions. Middleware acts as a bridge between dispatching an action and the moment it reaches the reducer. This makes it perfect for logging, error reporting, or handling advanced asynchronous workflows.

In this post, we’ll dive into middleware concepts and how to create custom middleware for advanced use cases.


What is Middleware in Redux?

Middleware is a function that intercepts dispatched actions before they reach the reducer. It provides a way to:

  1. Inspect: Check or modify the action.
  2. Log: Keep track of dispatched actions for debugging.
  3. Handle Async Logic: Like thunks or sagas for asynchronous workflows.

Common Middleware Use Cases

  1. Logging actions and state changes.
  2. Validating dispatched actions.
  3. Calling APIs or handling promises.
  4. Performing analytics tracking.

Built-in Middleware in Redux Toolkit

Redux Toolkit includes some default middleware, such as:

  • Redux Thunk (for async actions).
  • Serializable State Check (ensures state is serializable).
  • Immutable State Check (warns against state mutation).

You can customize the middleware stack while configuring the store.


Scenario: Logging Middleware

We’ll create a logging middleware that logs every dispatched action along with the state before and after the action is processed.


Step 1: Create Custom Middleware

A middleware is a function that receives store, next, and action:

const loggerMiddleware = (store) => (next) => (action) => {
  console.group(action.type);
  console.log('Previous State:', store.getState());
  console.log('Action:', action);
  const result = next(action); // Pass the action to the next middleware/reducer
  console.log('Next State:', store.getState());
  console.groupEnd();
  return result; // Return the result for further use (e.g., in async actions)
};
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • store.getState(): Retrieves the current state.
  • next(action): Passes the action to the next middleware or reducer.
  • console.group: Groups logs for better readability in the console.

Step 2: Integrate Middleware with the Store

When configuring the store, use the middleware property to add custom middleware:

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(loggerMiddleware),
});

export default store;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • getDefaultMiddleware: Adds the default middleware (like Redux Thunk).
  • .concat(loggerMiddleware): Appends custom middleware to the stack.

Step 3: Dispatch Actions to See the Middleware in Action

Use the existing counter app from previous examples. When you interact with the app (increment, decrement), you’ll see logs in the console showing the action type, previous state, and next state.


Advanced Middleware: Conditional Logic Example

Let’s create middleware to validate actions based on custom conditions.

Middleware: Action Validation

const validateActionMiddleware = (store) => (next) => (action) => {
  if (action.type === 'counter/incrementByAmount' && action.payload < 0) {
    console.error('Negative values are not allowed for incrementByAmount.');
    return; // Block the action
  }
  return next(action); // Allow the action if valid
};
Enter fullscreen mode Exit fullscreen mode

Integrate with Store

const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(loggerMiddleware, validateActionMiddleware),
});
Enter fullscreen mode Exit fullscreen mode

Step 4: Test Advanced Middleware

  1. Dispatch a valid action:
   dispatch(incrementByAmount(5)); // Works as expected
Enter fullscreen mode Exit fullscreen mode
  1. Dispatch an invalid action:
   dispatch(incrementByAmount(-3)); // Logs an error and blocks the action
Enter fullscreen mode Exit fullscreen mode

You’ll see the middleware intercept invalid actions and prevent state changes.


Middleware vs Thunks: When to Use What?

Feature Middleware Thunk
Purpose Intercepts actions for processing. Executes async logic before actions.
Use Cases Logging, analytics, validation. API calls, async workflows.
Integration Automatically applies to all actions. Explicitly dispatched in components.
Example Blocking invalid actions. Fetching API data.

Conclusion

Middleware in Redux provides a powerful way to handle cross-cutting concerns, such as logging, validation, and async logic. In this post, we covered:

  1. The concept of middleware.
  2. How to create and integrate custom middleware.
  3. Examples of logging and validation middleware.

In the next post, we’ll explore Redux-Saga for handling complex async workflows, and compare it with thunks to help you decide which to use for your projects.

Top comments (0)