My React Learning Note (3)

Topic: Starting from Redux

My React Learning Note (3)

Topic: Starting from Redux

Motivation

Redux is a state management library.

As the app gets larger, it is hard to update, manage the state.

We move all states in a store which is a massive object. This avoid the complexity that passing props to components that do not need them.

image-20211030164040827

image-20211030164105814 image-20211030164119824

Three principles:

  1. Single source of truth
  2. State is read only
  3. Changes using pure functions

Redux flow:

Actions ->(Middleware) -> Root Reducer -> Store -> DOM changes

Flux pattern:

Action -> Dispatcher -> Store -> View

There is only one way of dataflow! Instead of moving data all around!

1
2
3
4
5
6
7
8
9
10
11
//the outermost
ReactDOM.render(
// Redux Provider
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("root")
);

Actions

Action is to make sure to fire reducer in a right format

action is object that takes payload, type to update the specific reducer.

type is the instruction to perform. Every time the action returns a new object to the reducer to update the states.

1
2
3
4
5
6
7
8
9
10
11
const userReducer = (currentState, action) => {
switch(action.type){
case "SET_CURRENT_USER":
return {
...currentState,
currentUser: action.payload
}
default:
return currentState;
}
}

Reducer

Reducer is a function that manages the state. We can think of reducer as exposed api to manage the state stored in store

All these reducers then form a root reducer which is a representation of our app state. Then we pass these states to our component.

1
npm install redux redux-logger react-redux
1
2
3
4
5
6
7
8
9
10
11
12
13
// header.component.jsx
import { connect } from "react-redux";

//state: root reducer
const mapStateToProps = (state) => ({
//1. set the props name
//2. state = root reducer
//3. state.user = user reducer
//4. state.user.currentUser = currentUser in the user reducer!
currentUser: state.user.currentUser,
});
export default connect(mapStateToProps)(Header);

mapStateToProps

mapStateToProps?: (state, ownProps?) => Object

If a mapStateToProps function is specified, the new wrapper component will subscribe to Redux store updates. This means that any time the store is updated, mapStateToProps will be called. The results of mapStateToProps must be a plain object, which will be merged into the wrapped component’s props. If you don’t want to subscribe to store updates, pass null or undefined in place of mapStateToProps.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//state: root reducer
//put a props into the header component
const mapStateToProps = (state) => ({
//1. set the props name
//2. state = root reducer
//3. state.user = user reducer
//4. state.user.currentUser = currentUser in the user reducer!
currentUser: state.user.currentUser,
});
export default connect(mapStateToProps)(Header);

//or advanced destructrue
const mapStateToProps = ({ user: { currentUser }, cart: { hidden } }) => ({
currentUser,
hidden,
});

mapDispatchToProps

1
2
3
4
5
6
7
const mapDispatchToProps = (dispatch) => ({
setCurrentUser: (user) => dispatch(setCurrentUser(user)),
});

//param1: mapState, param2: mapDispatch
export default connect(null, mapDispatchToProps)(App);

If we do not supply mapDispatchToProps to connect. connect will put dispatch to props!

1
2
3
4
5
6
<CustomButton onClick={() => {
history.push("/checkout");
dispatch(toggleCartHidden());
}}>
Go to checkout
</CustomButton>

Redirect

1
<Route exact path="/signin" render={() => (this.props.currentUser? (<Redirect to='/' />) : (<SignInAndSignUpPage />))} />

find

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// cart.utils.js
export const addItemToCart = (cartItems, cartItemToAdd) => {
const existingCartItem = cartItems.find(
(cartItem) => cartItem.id === cartItemToAdd.id
);
if (existingCartItem) {
return cartItems.map((cartItem) =>
cartItem.id === cartItemToAdd.id
? { ...cartItem, quantity: cartItem.quantity + 1 }
: cartItem
);
} else {
return [...cartItems, { ...cartItemToAdd, quantity: 1 }];
}
};

selector and reduce

Selector: get a portion of state info for our need

first parameter will be accumulated

the second parameter is the element being traversed

1
2
3
4
5
6
7
const mapStateToProps = (state) => ({
itemCount: state.cart.cartItems.reduce(
(accumulatedQuantity, cartItem) =>
accumulatedQuantity + cartItem.quantity,
0
),
});

reselect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

//cart.selectors.js
import { createSelector } from "reselect";

const selectCart = (state) => state.cart;

//Memorized selector
export const selectCartItems = createSelector(
[selectCart],
(cart) => cart.cartItems
);

export const selectCartItemsCount = createSelector([selectCartItems], (cartItems) =>
cartItems.reduce(
(accumulatedQuantity, cartItem) => accumulatedQuantity + cartItem.quantity,
0
)
);

Then, we can replace mapStateToProps with selector!

1
2
3
4
5
6
7
const mapStateToProps = (state) => selectCartItems(state);

//itemCount can be then pass as props!
const mapStateToProps = (state) => ({
itemCount: selectCartItemsCount(state),
});

createStructuredSelector

1
2
3
4
const mapStateToProps = createStructuredSelector(
{
itemCount: selectCartItemsCount
})

withRouter

withRouter allows components not wrapped with <Route> access to this.props.history

1
export default withRouter(connect(mapStateToProps)(CartDropdown));

Redux Persist

https://www.npmjs.com/package/redux-persist

window.sessionStorage: stores for a session

window.localStorage: store until get cleared!

1
2
3
4
const myObject = {name: "alibaba"}
window.localStorage.setItem("myItem", JSON.stringify(myObject))

const myRetrieved = JSON.parse(window.localStorage.getItem("myItem"))
1
2
3
4
5
6
7
8
9
10
11
12
13
//store.js
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import { persistStore } from "redux-persist";
import rootReducer from "./root-reducer";

const middlewares = [logger];

const store = createStore(rootReducer, applyMiddleware(...middlewares));

//make store persistable
const persistor = persistStore(store);
export default store;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//root-reducer.js
import { combineReducers } from "redux";
import { persistReducer } from "redux-persist";
//import sessionStorage from "...";
import storage from "redux-persist/lib/storage";
import userReducer from "./user/user.reducer";
import cartReducer from "./cart/cart.reducer";

const persistConfig = {
key: "root",
storage,
whitelist: ["cart"],
};

const rootReducer = combineReducers({
user: userReducer,
cart: cartReducer,
});

export default persistReducer(persistConfig, rootReducer);


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!