Professional Documents
Culture Documents
Miguel Silva - Redux Introduction Curse in Markdown
Miguel Silva - Redux Introduction Curse in Markdown
## Table of Contents
* [Intro](#intro)
* [Actions](#actions)
* [Action Creators](#action-creators)
* [Reducer](#reducer)
* [Dispatch](#dispatch)
* [Combine Reducers](#combinereducers)
* [Redux Practice](#redux-practice)
* [Redux in React](#redux-in-react)
* [connect](#redux-in-react---connect)
* [mapStateToProps](#mapstatetoprops)
* [mapDispatchToProps](#mapdispatchtoprops)
* [useSelector](#useselector)
* [useDispatch](#usedispatch)
* [Redux Thunk](#redux-thunk)
## Intro
A way to practice pure functions and global state management without sate effects
* is a State management tool
> "I thought React already had satate, why do I need a separate tool to do it?" - you
Guiding Principles
* Redux puts your application data in global state. So if you need some data you can use Redux
to fetch that data and put it in the global context for you, then you can subscribe to the parts of the
global state that your component needs.
* If you want to modify global context, you need to emmit an action. That describes the kind of
change we want to do
* It gives that action to redux, and let redux do whatever necesary with that action , and the
component just keep reading state
Pure functions
The job of a reducer as a pure function is to take a previous version of state and an action and
determine a new value for state.
So as it's a pure function is must return a new version of state without modifying the previous one
Fundamentals
1. Actions & action creators
3. Reducers: take the current state and an action and they produce a new version of the state.
Restaurant Analogy
```javascript
const initialState = {
count: 0
switch(action.type) {
case "INCREMENT":
return {
count: state.count + 1
case "DECREMENT":
return {
count: state.count - 1
default:
return state
store.subscribe(() => {
console.log(store.getState())
})
store.dispatch({type: "INCREMENT"}) // 1
store.dispatch({type: "INCREMENT"}) // 2
store.dispatch({type: "DECREMENT"}) // 1
```
## Actions
```javascript
const action = {
```
```javascript
const action = {
function increment(){
return {
type: "INCREMENT"
```
```javascript
console.log(increment())
```
## Reducer
It it s a function that takes an old version of state and an action, and then it returns a new version of
state.
The job of the action is to describe how it takes the oldVersion of state and returns a new version.
```javascript
function increment() {
return {
type: "INCREMENT"
function decrement() {
return {
type: "DECREMENT"
switch(action.type) {
case "INCREMENT":
return {
count: state.count + 1
case "DECREMENT":
return {
count: state.count - 1
default:
return state
```
Whatever we return from the reducer is what is going to be stored in Redux's store
As the first time, `action` is null or undefined, we need a default value to pass the initial state
All the previous thing were more about the philosophy about redux
```javascript
console.log(store)
```
```js
dispatch: dispatch(action),
subscribe: subscribe(listener),
getState: getState(),
replaceReducer: replaceReducer(nextReducer),
```
Suscribe receives a function that is going to be called everytime the state changes
```javascript
store.subscribe(() => {
console.log(store.getState())
})
```
## Dispatch
It expects an action
```javascript
store.subscribe(() => {
console.log(store.getState())
})
store.dispatch({type: "INCREMENT"})
store.dispatch({type: "INCREMENT"})
store.dispatch({type: "INCREMENT"})
store.dispatch({type: "DECREMENT"})
// console.log 1, 2, 3, 2
```
Generally you don't hardcode the action, you use the actionCreator function:
So it should be:
```javascript
const redux.createStore(reducer)
store.subscribe(() => {
console.log(store.getState())
})
store.dispatch(increment())
store.dispatch(increment())
store.dispatch(increment())
store.dispatch(decrement())
// console.log 1, 2, 3, 2
```
Default case also prevents errors if we pass an unknow action to the producer like `{type: "WEIRD"}`
For example we can specify the quantity of increment. You can name it anyway but is a good practice to
use the word `payload`
```javascript
function increment(amount) {
return {
type: "INCREMENT",
payload: amount,
```
Now the reducer:
```javascript
switch(action.type) {
case "INCREMENT":
return {
case "DECREMENT":
return {
count: state.count - 1
default:
return state
store.subscribe(() => {
console.log(store.getState())
})
store.dispatch(increment(5))
```
I could use increment and rename it to changeCount and I can use it to increment or decrement. And
you can define a default
Example:
```javascript
function changeCount(amount = 1) {
return {
type: "CHANGE_COUNT",
payload: amount
switch(action.type) {
case "CHANGE_COUNT":
return {
default:
return state
store.subscribe(() => {
console.log(store.getState())
})
store.dispatch(changeCount(5))
```
```javascript
function addFavoriteThing(thing) {
return {
type: "ADD_FAVORITE_THING",
payload: thing
const initialState = {
count: 0,
favoriteThings: []
switch(action.type) {
case "CHANGE_COUNT":
return {
case "ADD_FAVORITE_THING":
return {
default:
return state
store.subscribe(() => {
console.log(store.getState())
})
store.dispatch(changeCount(2))
store.dispatch(addFavoriteThing("blahblahblah"))
```
It will show:
```
›{count: 2}
›{favoriteThings: ["blahblahblah"]}
```
```javascript
case "CHANGE_COUNT":
return {
...state,
case "ADD_FAVORITE_THING":
return {
...state,
default:
return state
store.subscribe(() => {
console.log(store.getState())
})
store.dispatch(changeCount(2))
store.dispatch(addFavoriteThing("blahblahblah"))
store.dispatch(addFavoriteThing("new"))
```
```
Output
```
extra. You can round case with {} to create a scope and reapeat const arrCopy in each case
```javascript
case "REMOVE_FAVORITE_THING": {
return {
...state,
favoriteThings: updatedArr
```
---
Practice with an object as property where we again need to use spread operator
## combineReducers
Get a cleaner code by dividing reducer in small reducers and joining them
* favoriteThings Reducer
* youtubeVideo Reducer
And create a file for each piece of state you want to maintain:
* count.js
* favoriteThings.js
* youTubeVideo.js
Each one will carry of their one action creators and it's reducer
```javascript
// redux/count.js
return {
type: "CHANGE_COUNT",
payload: amount
switch(action.type) {
case "CHANGE_COUNT":
default:
return count
}
}
```
```javascript
// redux/favoriteThings.js
return {
type: "ADD_FAVORITE_THING",
payload: thing
return {
type: "REMOVE_FAVORITE_THING",
payload: thing
switch(action.type) {
case "ADD_FAVORITE_THING":
case "REMOVE_FAVORITE_THING": {
return updatedArr
}
default:
return favoriteThings
```
```javascript
// redux/youTubeVideo.js
return {
type: "SET_YOUTUBE_TITLE",
payload: title
return {
type: "INCREMENT_VIEW_COUNT"
return {
type: "UPVOTE_VIDEO"
type: "DOWNVOTE_VIDEO"
const initialState = {
title: "",
viewCount: 0,
votes: {
up: 0,
down: 0
switch(action.type) {
case "INCREMENT_VIEW_COUNT":
return {
...youTubeVideo,
viewCount: youTubeVideo.viewCount + 1
case "SET_YOUTUBE_TITLE":
return {
...youTubeVideo,
title: action.payload
case "UPVOTE_VIDEO":
return {
...youTubeVideo,
votes: {
...youTubeVideo.votes,
up: youTubeVideo.votes.up + 1
case "DOWNVOTE_VIDEO":
return {
...youTubeVideo,
votes: {
...youTubeVideo.votes,
down: youTubeVideo.votes.down + 1
default:
return youTubeVideo
```
---
## combineReducers part2
creating a rootReducer
Really the only thing the app needs is the store, to dispatch, susbscribe, etc
```javascript
const rootReducer =
combineReducers({
count: countReducer,
favoriteThings: favoriteThingsReducer,
youTubeVideo: youTubeVideoReducer
})
```
```javascript
count,
favoriteThings,
youTubeVideo
})
```
```javascript
store.subscribe(() => {
console.log(store.getState())
})
```
```javascript
console.log(store)
```
```json
{dispatch: dispatch(action), subscribe: subscribe(listener), getState: getState(), replaceReducer:
replaceReducer(nextReducer)}
```
```javascript
store.dispatch(changeCount(43))
```
What redux do is run the root reducer that it runs all the 3 reducers
An adventage is that I can create same actions types for different reducers, so it will update all of those
properties
## Redux Practice
```javascript
store.dispatch(changeCount(42))
store.dispatch(addFavoriteThing("Door bells"))
store.dispatch(addFavoriteThing("Sleigh bells"))
store.dispatch(removeFavoriteThing("door bells"))
store.dispatch(incrementViewCount())
store.dispatch(upvoteVideo())
store.dispatch(incrementViewCount())
store.dispatch(upvoteVideo())
store.dispatch(incrementViewCount())
store.dispatch(upvoteVideo())
store.dispatch(downvoteVideo())
```
This doesn't mean that this is the correct and only way to organize redux
## Redux Practice 2
```javascript
// redux/user.js
return {
type: "SET_USER_DETAILS",
payload: user
return {
type: "REMOVE_USER_DETAILS"
}
}
switch(action.type) {
case "SET_USER_DETAILS":
return {
...user,
...action.payload
case "REMOVE_USER_DETAILS":
return null
default:
return user
```
in redux/index.js
```javascript
count: countReducer,
favoriteThings: favoriteThingsReducer,
youTubeVideo: youTubeVideoReducer,
user: userReducer
})
store.subscribe(() => {
console.log(store.getState())
})
```
In general index.js:
```javascript
store.dispatch(setUserDetails({
firstName: "Joe",
lastName: "Schmoe",
id: 1,
email: "joe@schmoe.com"
}))
store.dispatch(setUserDetails({
email: "joe.schmoe@gmail.com"
}))
store.dispatch(removeUserDetails())
```
## Redux in React
```javascript
// redux/index.js
function increment() {
return {
type: "INCREMENT"
function decrement() {
return {
type: "DECREMENT"
switch(action.type) {
case "INCREMENT":
return count + 1
case "DECREMENT":
return count - 1
default:
return count
```
```jsx
// index.js
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
```
* Higher-order component
* Pass 2 things:
1. What parts of the global state does this component wants access to?
* It then returns a *function* to which you pass the component you want to connect. When called, this
function creates a new component wrapping yours which passes the global state and 'dispatchable'
actions to your component via props
```jsx
connect(mapStateToPropsFun,
mapDispatchToPropsFunc)(Component)
```
In a component:
```jsx
// App.jsx
import React from "react"
function App(props) {
return (
<div>
<button>-</button>
<button>+</button>
</div>
```
## mapStateToProps
```jsx
function App(props) {
return (
<div>
<h1>{props.count}</h1>
<button>-</button>
<button>+</button>
</div>
// returns an object where the keys are the name of the prop your component wants,
// and the values are the actual parts of the global state your component wants
function mapStateToProps(globalState) {
return {
```
Intially it's 0 because in our counter we define that state is a number first 0
---
## mapDispatchToProps
```javascript
// redux/index.js
return {
type: "INCREMENT"
}
return {
type: "DECREMENT"
switch(action.type) {
case "INCREMENT":
return count + 1
case "DECREMENT":
return count - 1
default:
return count
```
```javascript
// App.js
function App(props) {
return (
<div>
<h1>{props.count}</h1>
<button onClick={props.dec}>-</button>
<button onClick={props.inc}>+</button>
</div>
function mapStateToProps(state) {
return {
count: state
const mapDispatchToProps = {
inc: increment,
dec: decrement
// or also
```
[React Documentation](https://react-redux.js.org/api/connect#connect)
## useSelector
```jsx
function App(props) {
return (
<div>
<h1>{count}</h1>
<button onClick={props.decrement}>-</button>
<button onClick={props.increment}>+</button>
</div>
```
## useDispatch
```javascript
function App(props) {
return (
<div>
<h1>{count}</h1>
</div>
```
[Article](https://thoughtbot.com/blog/using-redux-with-react-hooks)
---
## Redux Thunk
return {
type: "INCREMENT"
```
install redux-thunk
```jsx
setTimeout(() => {
dispatch({type: "INCREMENT"})
}, 1500)
}
return {
type: "DECREMENT"
switch(action.type) {
case "INCREMENT":
return count + 1
case "DECREMENT":
return count - 1
default:
return count
```
Case of use
```javascript
fetch(`baseUrl/${number}`)
.then(res => {
console.log(res)
dispatch({
type: "INCREMENT",
payload: res
})
})
return {
type: "DECREMENT"
switch(action.type) {
case "INCREMENT":
return count + 1
case "DECREMENT":
return count - 1
default:
return count
```