Basically, we manage global state through store
, action
, reducer
in redux. We can define our action
and reducer
first, dispatch
action
through synchronous operations, and then processed by the reducer
to update the store
so that components can access the state
from the store
.
But in practice, we may often need to handle asynchronous behavior, such as API requests, handling browser localStorage data, logging, etc. We hope that these behaviors or data can also be unified and managed by redux, rather than written in the Handling event or useEffect
of react, so that our react can be as simple and state more transparent and clear as possible.
Middleware
So what can we do?
We can use middleware between the action
and reducer
of the dispatch
to extend the content we want to execute. In addition to using the official redux-thunk
or using third-party redux-saga
or a middleware written by someone else, we can also implement our own middleware.
This article will implement a simple middleware in the redux toolkit to write localStorage. In the configureStore()
of the redux toolkit, besides automatically combining slice reducers, it also includes thunk
by default and automatically starts the redux DevTools.
Create Middleware
We first need to initialize middleware. Our middleware is called when each action
is dispatch
, that is to say, each action
will go through the middleware. If it matches the function type, it will execute what we want to execute, otherwise it will do nothing. But in either case, action
will be passed to the next middleware in the chain through return next(action)
, and if there is no next middleware, it will be handed over to reducer
for processing.
# Simplified Writing
const Middleware = (store) => (next) => (action) => {
//..
return next(action);
}
// Complete Writing
const Middleware = function(store) {
return function(next) {
return function(action) {
//..
};
};
};
localStorage
Suppose we have a user.js component with login and logout buttons respectively, and define our action and reducer in userSlice as follows. When we login, we will dispatch the login result to the store in the component, and then dispatch the logout result to the store when we logout.
import { createSlice } from "@reduxjs/toolkit";
const initialState = {login:false}
export const userSlice = createSlice({
name:"user",
initialState:{ value:initialState },
reducers:{
login:(state, action) => {
state.value = action.payload;
},
logout:(state, action) => {
state.value = action.payload;
},
}
})
export const { login, logout } = userSlice.actions;
export default userSlice.reducer;
We want the browser update to remain logged in, so we store the login result in the localStorage
of the browser when logging in, and clear the localStorage
on the browser when logging out, because localStorage
is a side effect, We will implement Middleware below to operate.
Implementing localStorage Middleware
In addition to subscribing to callbacks in the store, we can also perform this operation in middleware, or even use other permanent repositories, in this example we will implement a middleware. Using the action creator function of the redux toolkit contains a useful function match()
, we can use match()
without looking at the type of the function, if login.match(action)
is true, it will Execute the result of the login in our localStorage
, and if logout.match(action)
is true, the localStorage
will be cleared.
import { login, logout } from './features/user';
const userMiddleware = (store) => (next) => (action) => {
if (login.match(action)) {
localStorage.setItem('userState', JSON.stringify(action.payload));
} else if (logout.match(action)) {
localStorage.removeItem();
}
return next(action);
};
After that, we call the getDefaultMiddleware function in configureStore of redux toolkit, then we add the middleware we set by ourselves by default.
import { configureStore } from "@reduxjs/toolkit";
import userReducer, { login, logout } from './features/user';
const store = configureStore({
reducer:{
user:userReducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(userMiddleware)
});
This completes the preparation of a simple middleware!