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!