news-pubish-management(7)--manage state with redux

Next we focus on the state management, here we use classic Redux.

1. setup redux

1
2
3
4
5
yarn add redux react-redux
mkdir src/redux
touch src/redux/store.js
mkdir src/redux/reducers
touch src/redux/reducers/CollapsedReducer.js

redux principles
. Single source of truth
. State is read-only
. Changes are made with pure functions

2. Finish the collapsed in both TopHeader and SideMenu components.

CollapsedReducer.js

1
2
3
export const CollapsedReducer = (prevState = { isCollapsed: false }, action) => {
return prevState;
}
1
2
3
4
5
6
7
8
9
10
import { createStore, combineReducers } from 'redux';
import { CollapsedReducer } from './reducers/CollapsedReducer';

const reducer = combineReducers({
CollapsedReducer
})

const store = createStore(reducer);

export default store;

we have a single store now, it mean if we can get back our store, then we can do store.dispatch or store.subscribe operations. but how to get store in our component?
Go to App.js, make our IndexRouter in the Provider section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react'
import IndexRouter from './router/IndexRouter'
import { Provider } from 'react-redux'
import store from './redux/store'

import './App.css'

export default function App() {
return (
<Provider store={store}>
<IndexRouter />
</Provider>
)
}

Our SideMenu and TopHeader will use the store. so go to TopHeader first, because Topheader is publish the state.Its response is Dispatch the state to store.
In TopHeader, we use connect to make TopHeader as child components, we can get value from parameter of props.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { connect } from 'react-redux';
...

function TopHeader(props) {
...
}

const mapStateToProps = () => {
return {
testValue:'testValue',
}
}

export default connect(mapStateToProps)(TopHeader);

In previous version, we use component’s state, now we have props. and props contain isCollapsed, so we remove the component state.

in TopHeader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React from 'react'
...
import { connect } from 'react-redux';
...

function TopHeader(props) {
// NOTE: changed to redux, state is not neccessary.
// const [collapsed, setCollapsed] = useState(false)
...
const changeCollapsed = () => {
// NOTE: changed to redux, state is not neccessary.
// setCollapsed(!collapsed);
props.changeCollapsed();
}
}


const mapStateToProps = ({ CollapsedReducer: { isCollapsed } }) => {
return {
isCollapsed
}
}

const mapDispatchToProps = {
changeCollapsed() {
return {
type: "change_collapsed",
// payload:''
}
}
}

export default connect(mapStateToProps,mapDispatchToProps)(TopHeader);

when user click the icon in TopHeader, the workflow go to store, and store find out the CollapsedReducer, then excute the logic to change to new state. below are the CollapsedReducer.js content:

1
2
3
4
5
6
7
8
9
10
11
12
export const CollapsedReducer = (prevState = { isCollapsed: false }, action) => {
// console.log(action);
const { type } = action;
switch (type) {
case 'change_collapsed':
let newState = { ...prevState };
newState.isCollapsed = !newState.isCollapsed;
return newState;
default:
return prevState;
}
}

Back to SideMedu, SideMenu retrieve the state to collapse or expand himself. We also connect SideMenu first. It doesn’t need Dispatch anything, so our changing in SideMenu looks like:

1
2
3
4
5
6
7
8
9
10
import { connect } from 'react-redux';
...
function SideMenu(props) {
...
<Sider trigger={null} collapsible collapsed={props.isCollapsed}>
}

const mapStateToProps = ({ CollapsedReducer: { isCollapsed } }) => ({ isCollapsed });

export default connect(mapStateToProps)(SideMenu);

3. Add spin when retrieve data from backend db.

Go to NewsRouter.js

1
2
3
4
5
6
7
import { Spin } from 'antd';
...

return (
<Spin size='large' spinning={true}>
...
</Spin>

if the spinning is false then the Spin is displayed, otherwise will showing.

every call will showing, we can perform it in axios intercepter.

Go to http.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
// Show Spin
return config;
}, function (error) {
// Do something with request error
// Hide Spin
return Promise.reject(error);
});

// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
// Hide Spin
return response;
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
// Hide Spin
return Promise.reject(error);
});
1
touch src/redux/reducers/LoadingReducer.js

copy CollapsedReducer.js and modified as below

1
2
3
4
5
6
7
8
9
10
11
12
export const LoadingReducer = (prevState = { isLoading: false }, action) => {
// console.log(action);
const { type, payload } = action;
switch (type) {
case 'change_loading':
let newState = { ...prevState };
newState.isLoading = payload;
return newState;
default:
return prevState;
}
}

also we need login this reducer to store.js.

1
2
3
4
5
6
7
8
9
10
11
12
import { createStore, combineReducers } from 'redux';
import { CollapsedReducer } from './reducers/CollapsedReducer';
import { LoadingReducer } from './reducers/LoadingReducer';

const reducer = combineReducers({
CollapsedReducer,
LoadingReducer
})

const store = createStore(reducer);

export default store;

add connect to NewsRouter.js

1
2
3
4
5
6
7
8
9
10
11
import { connect } from 'react-redux'
...
function NewsRouter(props) {
...
<Spin size='large' spinning={props.isLoading}>
...
}

const mapStateToProps = ({ LoadingReducer: { isLoading } }) => ({ isLoading });

export default connect(mapStateToProps)(NewsRouter);

Back to http.js to dispatch isLoading to store.

1
2
3
4
5
6
import store from '../redux/store';
...
store.dispatch({
type:'change_loading',
payload: true,
});

4. Persist redux state

We chose redux persist to implement this feature.

1
yarn add redux-persist

we have to modify our store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { createStore, combineReducers } from 'redux';
import { CollapsedReducer } from './reducers/CollapsedReducer';
import { LoadingReducer } from './reducers/LoadingReducer';

import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web

const reducer = combineReducers({
CollapsedReducer,
LoadingReducer
})

const persistConfig = {
key: 'collapsed',
storage,
blacklist: ['LoadingReducer']
}

const persistedReducer = persistReducer(persistConfig, reducer)

const store = createStore(persistedReducer)
const persistor = persistStore(store)

export {
store,
persistor,
};

Go to App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import { store, persistor } from './redux/store'
import { PersistGate } from 'redux-persist/integration/react'
...

export default function App() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
...
</PersistGate>
</Provider>
)
}