With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it also as a popular state management solution for React apps when combined with React-Redux.
In this article, we’ll look at how to create our own Redux middleware.
Middleware
Middleware is some code that we can put between dispatching an action and the moment that the action reaches the reducer.
To do this in a clean way, we define a function that takes the store
as a parameter, which returns a function that takes the next
function, which is the same as the dispatch
function, which then returns a function that takes an action
parameter and returns next(action)
.
We can do that as follows:
import { createStore, applyMiddleware } from "redux";
function countReducer(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
const logger = store => next => action => {
console.log("dispatching", action);
let result = next(action);
console.log("next state", store.getState());
return result;
};
let store = createStore(countReducer, applyMiddleware(logger));
store.subscribe(() => store.getState());
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "DECREMENT" });
In the code above, we have the logger
middleware, which is a function that takes the store
parameter which holds the Redux store itself, which then returns a function that takes the next
function, which the same as store.dispatch
. This function then returns a function that takes the action
parameter, which is our action object which we called dispatch
with.
In the function that takes action
, first, we log action
, then we call next
with action
, then we take the returned result and assigned it to result
.
Then we log store.getState()
, and return result
.
To use the logger
middleware we just defined, we write:
let store = createStore(countReducer, applyMiddleware(logger));
Then when we run dispatch
we’ll see the console.log
output from the logger.
Returning Non-Plain Objects with Middleware
We don’t have to return plain objects with middleware.
For example, we can also return a promise as follows:
const promiseMiddleware = store => next => action => {
if (!(action instanceof Promise)) {
return next(action);
}
return (async () => {
const resolvedAction = await Promise.resolve(action);
store.dispatch(resolvedAction);
})();
};
In the code above, we returned a promise, which isn’t a plain object.
All it does is that if we pass in a promise into dispatch
, it’ll resolve the promise and then call store.dispatch
with the resolved action.
We can then call the applyMiddleware
function when the createStore
function is called.
Note that we called our async function, we don’t just return it. We have to call store.dispatch
so that the values from our actions will be set in the store.
Defining and Using Multiple Middlewares
We can create and apply multiple middlewares when we call applyMiddleware
. To do this, we just pass it all the middlewares for applyMiddleware
.
For example, if we want to apply all the middlewares in the examples we have above, we can write:
import { createStore, applyMiddleware } from "redux";
function countReducer(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
const logger = store => next => action => {
console.log("dispatching", action);
let result = next(action);
console.log("next state", store.getState());
return result;
};
const promiseMiddleware = store => next => action => {
if (!(action instanceof Promise)) {
return next(action);
}
return (async () => {
const resolvedAction = await Promise.resolve(action);
store.dispatch(resolvedAction);
})();
};
let store = createStore(
countReducer,
applyMiddleware(promiseMiddleware, logger)
);
store.subscribe(() => store.getState());
store.dispatch(Promise.resolve({ type: "INCREMENT" }));
store.dispatch({ type: "DECREMENT" });
Since we have our promiseMiddleware
, we can now pass in promises to dispatch
and set the value in the store.
Therefore, we can write:
store.dispatch(Promise.resolve({ type: "INCREMENT" }));
store.dispatch({ type: "DECREMENT" });
and we won’t get an error.
With the logger
middleware, we get console.log
output.
Conclusion
We can define middleware to do more than what we can do with Redux itself.
To do this, we define a function that takes the store
as a parameter, which returns a function that takes the next
function, which is the same as the dispatch
function, which then returns a function that takes an action
parameter and returns next(action)
.
The function has to call dispatch
so that the action object can be propagated to the reducer.
We can chain middlewares with the applyMiddleware
function, which takes one or more middlewares.