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:
- Inspect: Check or modify the action.
- Log: Keep track of dispatched actions for debugging.
- Handle Async Logic: Like thunks or sagas for asynchronous workflows.
Common Middleware Use Cases
- Logging actions and state changes.
- Validating dispatched actions.
- Calling APIs or handling promises.
- 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)
};
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;
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
};
Integrate with Store
const store = configureStore({
reducer: {
counter: counterReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(loggerMiddleware, validateActionMiddleware),
});
Step 4: Test Advanced Middleware
- Dispatch a valid action:
dispatch(incrementByAmount(5)); // Works as expected
- Dispatch an invalid action:
dispatch(incrementByAmount(-3)); // Logs an error and blocks the action
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:
- The concept of middleware.
- How to create and integrate custom middleware.
- 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)