How to Implement Redux Middleware in React
Redux middleware provides a powerful way to extend Redux’s capabilities by intercepting actions before they reach the reducer. This article explains how to implement Redux middleware in a React application, covering the integration of third-party middleware, configuring modern Redux Toolkit, and writing your own custom middleware from scratch.
What is Redux Middleware?
In Redux, middleware sits between the moment an action is dispatched
and the moment it reaches the reducer. It is commonly used for side
effects, such as asynchronous API calls (using
redux-thunk), logging state changes (using
redux-logger), or routing.
Implementing Middleware with Redux Toolkit
Redux Toolkit (RTK) is the recommended way to write Redux logic
today. It simplifies store setup and automatically includes default
middleware like redux-thunk for asynchronous
operations.
To add custom or third-party middleware to your Redux Toolkit store,
use the middleware option in
configureStore.
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';
import logger from 'redux-logger'; // Example of third-party middleware
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(logger),
});
export default store;In this setup, getDefaultMiddleware() returns an array
containing the default middleware provided by RTK. The
.concat() method is used to safely append new middleware
without mutating the default array.
Writing Custom Middleware
Sometimes you need to write custom logic to handle specific actions. A Redux middleware is structured as a series of three nested functions (often referred to as a curried function):
- Outer function: Receives the store’s
dispatchandgetStatemethods. - Middle function: Receives the
nextdispatch function. - Inner function: Receives the
actionbeing dispatched.
Here is an example of a custom logging middleware:
const customLoggerMiddleware = (store) => (next) => (action) => {
console.log('Dispatching action:', action);
// Pass the action to the next middleware or reducer
const result = next(action);
console.log('Next state:', store.getState());
return result;
};
export default customLoggerMiddleware;To implement this custom middleware in your Redux Toolkit store:
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';
import customLoggerMiddleware from './customLoggerMiddleware';
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(customLoggerMiddleware),
});
export default store;Implementing Middleware in Legacy Redux (Using applyMiddleware)
If you are working with a legacy codebase that uses the core
redux package instead of Redux Toolkit, you must use the
applyMiddleware function from the redux
library.
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducer';
import thunk from 'redux-thunk';
import customLoggerMiddleware from './customLoggerMiddleware';
const store = createStore(
rootReducer,
applyMiddleware(thunk, customLoggerMiddleware)
);
export default store;Connecting the Store to your React Application
Once the store is configured with your middleware, connect it to your
React components using the Provider component from the
react-redux library.
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);By wrapping your application in the Provider, any action
dispatched from your React components using useDispatch
will now pass through your implemented middleware before updating the
store.