[React]: Redux Basics
React
11/08/2019
Redux
As apps get bigger, states of the app will become more complex and difficult to understand the flow of data.
Redux is a React library which allows react state to be more scalable.
Why Redux?
- Helps with state management
- Efficient for sharing data between components
- Predictable statement management with 3 principles
- Single source of truth
- State is read only (immutability)
- Changes made by pure functions
Instead of each component holding a different state, Redux allows to have one huge object(state) that describes the entire app and can be passed down to whichever component that needs it.
Redux Flow
- Action
- What user does
- Root Reducer (App state)
- Pure function that receives action. Returns updated state(store)
- Store
- Describes what app should look like
- DOM changes
+) Middleware receives action object before reaching root reducer, do something with it, then, pass it onto root reducer.
Flux Pattern vs MVC
Flux Pattern
Redux uses a flux pattern that's also unidirectional data flow.
MVC
On the other hand, MVC model uses
When user makes an action, based on what controller says, we update the model(state), then it updates the view.
The controller changes different models, and each model can change different views which may trigger other models to change--the relationship would be very difficult to understand in larger apps.
What reducer looks like
Reducer is a pure function that represents all of the states of our application.
// Initial state of reducer just like this.state in constructorconst INITIAL_STATE { currentUser: null};// If state is undefined, set it to INITIAL_STATE (ES6 feature)const userReducer = (state = INITIAL_STATE, action) = > { switch (action.type) { case 'SET_CURRENT_USER': return { // return new obj to allow re-render ...state, currentUser: action.payload // return state.currentUser = action.payload; // ^ the same obj so it won't re-render }; default: return state; // If action doesn't match }};export default userReducer;
Note: if/else statements are also okay
Installing Redux Libraries
$ npm i redux redux-logger react-redux
In index.js
// ...import { Provider } from "react-redux"// ...ReactDOM.render( <Provider> <BrowserRouter> <App />> </BrowserRouter> </Provider>, document.getElementById("root"))
Wrap around the entire app since we want to have access to store object from anywhere.
How root reducer looks like
import { combineReducers } from "redux"import userReducer from "./user.reducer"
// All full redux object is one big json objectexport default combineReducers({ user: userReducer,})
What store looks like
import { createStore, applyMiddleware } from "redux"import logger from "redux-logger"import rootReducer from "./root-reducer"
const middlewares = [logger]const store = createStore(rootReducer, applyMiddleware(...middlewares))
export default store
Back in index.js
// ...import { Provider } from "react-redux"import store from "./redux/store"// ...ReactDOM.render( <Provider store={store}> <BrowserRouter> <App />> </BrowserRouter> </Provider>, document.getElementById("root"))
import & pass into provider
Create actions
export const setCurrentUser = user => ({ type: "SET_CURRENT_USER", payload: user,})
In header component
// ...import { connect } from "react-redux"
// state is root reducerconst mapStateToProps = state => ({ curUser: state.user.curUser,})export default connect(mapStateToProps)(Header)
Updating Reducer value in App.js
import { connect } from "react-redux";import { setCurrentUser } from "/user.actions/";// ...class App extends React.Component { unsubscribeFromAuth = null;
componentDidMount() { const { setCurrentUser } = this.props; this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => { // set state from data in db if (userAuth) { const userRef = await createUserProfileDocument(userAuth); userRef.onSnapshot(snapShot => { setCurrentUser({ id: snapShot.id, ...snapShot.data() }); }); } else { setCurrentUser(userAuth); // setting it to null on logging out } }); } // ... render() { return ( <div> <Header /> { // ... } </div> ); }}const mapDispatchToProps = dispatch => ({ setCurrentUser: user => dispatch(setCurrentUser(user))});export default connect( null, mapDispatchToProps)(App);
Header is no longer dependent on App.js, but App updates user reducer values so header can get the latest user property.
Now you can see the log on the console
Redirecting
import { Switch, Route, Redirect } from "react-router-dom"
// ...class App extends React.Component { render() { return ( <div> <Route exact path="/signin" render={() => this.props.currentUser ? ( <Redirect to="/" /> ) : ( <SignInAndSignUpPage /> ) } /> </div> ) }}const mapStateToProps = ({ user }) => ({ currentUser: user.currentUser,})
const mapDispatchToProps = dispatch => ({ setCurrentUser: user => dispatch(setCurrentUser(user)),})export default connect(mapStateToProps, mapDispatchToProps)(App)