[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

3 13

  • 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.

3 12

MVC

On the other hand, MVC model uses

3 11

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.

JSX
// Initial state of reducer just like this.state in constructor
const 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

TEXT
$ npm i redux redux-logger react-redux

In index.js

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

JS
import { combineReducers } from "redux"
import userReducer from "./user.reducer"
// All full redux object is one big json object
export default combineReducers({
user: userReducer,
})

What store looks like

JS
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

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

JS
export const setCurrentUser = user => ({
type: "SET_CURRENT_USER",
payload: user,
})

In header component

JSX
// ...
import { connect } from "react-redux"
// state is root reducer
const mapStateToProps = state => ({
curUser: state.user.curUser,
})
export default connect(mapStateToProps)(Header)

Updating Reducer value in App.js

JSX
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

JSX
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)

WRITTEN BY

Keeping a record