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.
Three principles:
Single source of truth
State is read only
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!
ReactDOM.render( <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 import { connect } from "react-redux" ;const mapStateToProps = (state ) => ({ 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 const mapStateToProps = (state ) => ({ currentUser: state.user.currentUser, });export default connect(mapStateToProps)(Header);const mapStateToProps = ({ user: { currentUser }, cart: { hidden } } ) => ({ currentUser, hidden, });
mapDispatchToProps
1 2 3 4 5 6 7 const mapDispatchToProps = (dispatch ) => ({ setCurrentUser: (user ) => dispatch(setCurrentUser(user)), });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 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 import { createSelector } from "reselect" ;const selectCart = (state ) => state.cart;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);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 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));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 import { combineReducers } from "redux" ;import { persistReducer } from "redux-persist" ;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);