In this post we will talk about middleware in Redux and how to make them composable, we will also explain step by step how applyMiddleware
works to wrap the store's dispatch and add functionality to it.
Middleware
A middleware is just a function that allows us to wrap the store's dispatch method to add logic before and after dispatching an action.
To do this, middleware must wrap the store's dispatch method, so middleware can be composed of other functions, either other middleware or the dispatch method. This is why a middleware has the following shape:
type MiddlewareAPI = { dispatch: Dispatch, getState: () => State }
type Middleware = (api: MiddlewareAPI) => (next: Dispatch) => Dispatch
We can see that the first function receive a “store” (MiddlewareAPI
) as parameter, so we can pass one as an argument. Later I will explain in detail how it works.
applyMiddleware
applyMiddleware(...middleware)
is a special type of enhancer
, so it is also a higher-order function, this enhancer combines all the middlewares that are passed as parameters to create a single middleware that wraps the store's dispatch method. Let's see how it chains the middleware.
For this example we will use the following three middleware:
const firstMiddleware = (store) => (next) => (action) => {
console.log("First middleware");
const result = next(action);
console.log("First middlware done");
return result;
};
const secondMiddleware = (store) => (next) => (action) => {
console.log("Second middleware");
const result = next(action);
console.log("Second middlware done");
return result;
};
const thirdMiddleware = (store) => (next) => (action) => {
console.log("Third middleware");
const result = next(action);
console.log("Third middlware done");
return result;
};
As we have seen, a middleware receives an object similar to a store and returns a function that receive the next middleware as a parameter, in order to make them composable, so we could do the following.:
import { createStore, compose } from "redux";
const store = createStore(rootReducer, initialState);
const firstChain = firstMiddleware(store)
const secondChain = secondMiddleware(store)
const thirdChain = thirdMiddleware(store)
// Compose the middlewares and add the store dispatch as last "next" parameter
const enhancedDispatch = firstChain(secondChain(thirdChain(store.dispatch)))
// Another way to do this is to use the compose function
const enhancedDispatch = compose(firstChain, secondChain, thirdChain)(newStore.dispatch)
What has been achieved by doing this is to chain the middleware and have the last middleware dispatch the action, since the latter returns the following function:
(action) => {
console.log("Third middleware");
// next === store.dispatch
const result = next(action);
console.log("Third middlware done");
return result;
}
As the last parameter is the dispatch method, the return of the last middleware is the function we see above, this function is the value of the next
parameter of the previous middleware and so on, this way we obtain an "enhanced dispatch" where the action goes through all the middleware.
The following diagram shows how the middleware composition works:
Add middleware to a store enhancer
So far we have seen how to compose the middleware and create an enhanced dispatch, so the next step is to add it to the store so that the middleware is executed every time an action is dispatched.
To do this, we going to create an applyMiddleware
function that returns a store enhancer (Post about enhancer):
import { createStore, compose } from "redux";
// Receive middleware as parameter and return a store enhancer
const applyMiddleware = (...middleware) => (createStore) => {
return (reducer, initialState, enhancer) => {
const newStore = createStore(reducer, initialState, enhancer);
const middlewareChain = middleware.map((middleware) =>
middleware(newStore)
);
const enhancedDispatch = compose(...middlewareChain)(newStore.dispatch);
return {
...newStore,
dispatch: enhancedDispatch
};
};
};
// Create a store using the applyMiddleware function
const store = createStore(userReducer, initialState,
applyMiddleware(firstMiddleware, secondMiddleware, thirdMiddleware)
);
The applyMiddleware
function receives an array of middleware as parameter and returns an enhancer, which will be passed as the third parameter of the function createStore
and then we pass the new store as parameter of each middleware to obtain the function that will allow us to compose each middleware ((next) => {...}
) and as we saw before we pass the dispatch method as parameter of the last middleware. Finally, we return a new store whose dispatch method will be composed of all middleware.
From now on, every time an action is dispatched, the following flow will be executed.
Top comments (0)