Download as pdf or txt
Download as pdf or txt
You are on page 1of 39

# Redux

## Table of Contents

* [Intro](#intro)

* [Plain JS Redux - Overview](#plain-js-redux---overview)

* [Actions](#actions)

* [Action Creators](#action-creators)

* [Reducer](#reducer)

* [Creating the store](#creating-the-store)

* [Suscribe & getState](#suscribe--getstate)

* [Dispatch](#dispatch)

* [Payload in action creators](#payload-in-action-creators)

* [Handling more complex state](#handling-more-complex-state)

* [Combine Reducers](#combinereducers)

* [Redux Practice](#redux-practice)

* [Redux Practice 2](#redux-practice-2)

* [Redux in React](#redux-in-react)

* [connect](#redux-in-react---connect)

* [mapStateToProps](#mapstatetoprops)

* [mapDispatchToProps](#mapdispatchtoprops)

* [useSelector](#useselector)

* [useDispatch](#usedispatch)

* [Redux Thunk](#redux-thunk)

## Intro

* Redux was created before ReactJS Context

A way to practice pure functions and global state management without sate effects
* is a State management tool

What about React state?

> "I thought React already had satate, why do I need a separate tool to do it?" - you

* How to handle state that your whole application needs

* Lack of organization and clarity

Guiding Principles

1. A single source of truth ("global state")

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

2. State is read-only ("actions")

* 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

3. Changes are made with pure functions ("reducers")

Pure functions

1. Always return the same result for same input

2. It makes no alterations outside itself

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

2. Dispatch (vehicle for taking the action to the reducers)

3. Reducers: take the current state and an action and they produce a new version of the state.

4. Store (collection of these things)

Restaurant Analogy

1. Your Order => "action"

2. Restaurant server => "dispatch"

3. Chef => "reducer"

You can also use Redux in plain JS

## Plain JS Redux - Overview

```javascript

const redux = require("redux")

const initialState = {

count: 0

function reducer(state = initialState, action) {

switch(action.type) {

case "INCREMENT":

return {

count: state.count + 1

case "DECREMENT":
return {

count: state.count - 1

default:

return state

const store = redux.createStore(reducer)

store.subscribe(() => {

console.log(store.getState())

})

store.dispatch({type: "INCREMENT"}) // 1

store.dispatch({type: "INCREMENT"}) // 2

store.dispatch({type: "DECREMENT"}) // 1

```

## Actions

```javascript

const redux = require("redux")

const action = {

type: "ADD_ONE" // or INCREMENT

```

Convention is to use upper-case with dash

The action is describing the change you want to make


### Action Creators

```javascript

const redux = require("redux")

const action = {

type: "INCREMENT" // or INCREMENT

function increment(){

return {

type: "INCREMENT"

```

Is a function that returns an action

```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

const redux = require("redux")

function increment() {
return {

type: "INCREMENT"

function decrement() {

return {

type: "DECREMENT"

function reducer(state = {count: 0}, action) {

// return new state based on the incoming action.type

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

## Creating the store

All the previous thing were more about the philosophy about redux

```javascript

const store = redux.createStore(reducer)

console.log(store)

```

store will contain:

```js

dispatch: dispatch(action),

subscribe: subscribe(listener),

getState: getState(),

replaceReducer: replaceReducer(nextReducer),

```

## Suscribe & getState

Suscribe receives a function that is going to be called everytime the state changes

```javascript

const store = redux.createStore(reducer)

store.subscribe(() => {

console.log(store.getState())

})

```

`getState()` gets the currents state of the application data


Usually things like subscribe and getState are going to be handled for us under the hook, but still can be
circumstances where you can use them.

## Dispatch

Dispatch is like the waiter at the restaurant

It expects an action

```javascript

const store = redux.createStore(reducer)

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

```

With action creators you avoid typing errors in types.

Default case also prevents errors if we pass an unknow action to the producer like `{type: "WEIRD"}`

So all this is the main philosophy of Redux

## Payload in action creators

payload is like additional info.

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

function reducer(state = {count: 0}, action) {

// return new state based on the incoming action.type

switch(action.type) {

case "INCREMENT":

return {

count: state.count + action.payload

case "DECREMENT":

return {

count: state.count - 1

default:

return state

const store = redux.createStore(reducer)

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

const redux = require("redux")

function changeCount(amount = 1) {

return {

type: "CHANGE_COUNT",

payload: amount

function reducer(state = {count: 0}, action) {

// return new state based on the incoming action.type

switch(action.type) {

case "CHANGE_COUNT":

return {

count: state.count + action.payload

default:

return state

const store = redux.createStore(reducer)

store.subscribe(() => {

console.log(store.getState())

})

store.dispatch(changeCount(5))
```

## Handling more complex state

Redux will be the single source of truth

See first what happens in this example:

```javascript

function addFavoriteThing(thing) {

return {

type: "ADD_FAVORITE_THING",

payload: thing

const initialState = {

count: 0,

favoriteThings: []

function reducer(state = initialState, action) {

switch(action.type) {

case "CHANGE_COUNT":

return {

count: state.count + action.payload

case "ADD_FAVORITE_THING":

return {

// this is for not mutating the prev array


favoriteThings: [...state.favoriteThings, action.payload]

default:

return state

const store = redux.createStore(reducer)

store.subscribe(() => {

console.log(store.getState())

})

store.dispatch(changeCount(2))

store.dispatch(addFavoriteThing("blahblahblah"))

```

It will show:

```

›{count: 2}

›{favoriteThings: ["blahblahblah"]}

```

You need to combine oldVersion of state with new version.

We need to specify all properties in state

A corrected version of reducer is:

```javascript

function reducer(state = initialState, action) {


switch(action.type) {

case "CHANGE_COUNT":

return {

...state,

count: state.count + action.payload

case "ADD_FAVORITE_THING":

return {

...state,

favoriteThings: [...state.favoriteThings, action.payload]

default:

return state

const store = redux.createStore(reducer)

store.subscribe(() => {

console.log(store.getState())

})

store.dispatch(changeCount(2))

store.dispatch(addFavoriteThing("blahblahblah"))

store.dispatch(addFavoriteThing("new"))

```

```

Output

›{count: 2, favoriteThings: []}


›{count: 2, favoriteThings: ["blahblahblah"]}

›{count: 2, favoriteThings: ["blahblahblah", "new"]

```

extra. You can round case with {} to create a scope and reapeat const arrCopy in each case

```javascript

case "REMOVE_FAVORITE_THING": {

const arrCopy = [...state.favoriteThings]

const updatedArr = state.favoriteThings.filter(thing => thing.toLowerCase() !==


action.payload.toLowerCase())

return {

...state,

favoriteThings: updatedArr

```

---

## even more complex state

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

Actually reducer is getting 3 states levels updating

We need to create 3 reducers:


* countReducer

* favoriteThings Reducer

* youtubeVideo Reducer

So we will create a redux folder

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

export function changeCount(amount = 1) {

return {

type: "CHANGE_COUNT",

payload: amount

export function countReducer(count = 0, action) {

switch(action.type) {

case "CHANGE_COUNT":

return count + action.payload

default:

return count

}
}

export default countReducer

```

```javascript

// redux/favoriteThings.js

export function addFavoriteThing(thing) {

return {

type: "ADD_FAVORITE_THING",

payload: thing

export function removeFavoriteThing(thing) {

return {

type: "REMOVE_FAVORITE_THING",

payload: thing

export default function favoriteThingsReducer(favoriteThings = [], action) {

switch(action.type) {

case "ADD_FAVORITE_THING":

return [...favoriteThings, action.payload]

case "REMOVE_FAVORITE_THING": {

const updatedArr = favoriteThings.filter(thing => thing.toLowerCase() !==


action.payload.toLowerCase())

return updatedArr
}

default:

return favoriteThings

```

```javascript

// redux/youTubeVideo.js

export function setYouTubeTitle(title) {

return {

type: "SET_YOUTUBE_TITLE",

payload: title

export function incrementViewCount() {

return {

type: "INCREMENT_VIEW_COUNT"

export function upvoteVideo() {

return {

type: "UPVOTE_VIDEO"

export function downvoteVideo() {


return {

type: "DOWNVOTE_VIDEO"

const initialState = {

title: "",

viewCount: 0,

votes: {

up: 0,

down: 0

export default function youTubeVideoReducer(youTubeVideo = initialState, action) {

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

Inside /redux create index.js

It's going to be the entry point of redux

1. import the separte reducers

2. combine the reducres into a single state tree


3. create the store

4. export the store

Really the only thing the app needs is the store, to dispatch, susbscribe, etc

```javascript

const redux = require('redux')

const {combineReducers, createStore} = redux

import countReducer from "./count"

import favoriteThingsReducer from "./favoriteThings"

import youTubeVideoReducer from "./youTubeVideo"

const rootReducer =

// object represents the object of the global state

combineReducers({

count: countReducer,

favoriteThings: favoriteThingsReducer,

youTubeVideo: youTubeVideoReducer

})

```

Another option is to export default with other names:

```javascript

const redux = require("redux")

const {combineReducers, createStore} = redux

import count from "./count"


import favoriteThings from "./favoriteThings"

import youTubeVideo from "./youTubeVideo"

// combine the reducers into a single state tree

const rootReducer = combineReducers({

count,

favoriteThings,

youTubeVideo

})

```

So when we create store:

```javascript

const store = createStore(rootReducer)

store.subscribe(() => {

console.log(store.getState())

})

export default store

```

Now in general index.js

```javascript

// grab default import

import store from './redux/index'

console.log(store)

```

```json
{dispatch: dispatch(action), subscribe: subscribe(listener), getState: getState(), replaceReducer:
replaceReducer(nextReducer)}

```

```javascript

// grab default import

import store from './redux/index'

import {changeCount} from './redux/count'

store.dispatch(changeCount(43))

```

What redux do is run the root reducer that it runs all the 3 reducers

So for everything it will use default state except for count

An adventage is that I can create same actions types for different reducers, so it will update all of those
properties

## Redux Practice

```javascript

import store from "./redux"

import {changeCount} from "./redux/count"

import {addFavoriteThing, removeFavoriteThing} from "./redux/favoriteThings"

import {setYouTubeTitle, incrementViewCount, upvoteVideo, downvoteVideo} from


"./redux/youTubeVideo"

store.dispatch(changeCount(42))

store.dispatch(addFavoriteThing("Door bells"))

store.dispatch(addFavoriteThing("Sleigh bells"))
store.dispatch(removeFavoriteThing("door bells"))

store.dispatch(setYouTubeTitle("Learning Redux is Fun!"))

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

Store information about user

```javascript

// redux/user.js

export function setUserDetails(user) {

return {

type: "SET_USER_DETAILS",

payload: user

export function removeUserDetails() {

return {

type: "REMOVE_USER_DETAILS"

}
}

export default function userReducer(user = null, action) {

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

const redux = require("redux")

const {combineReducers, createStore} = redux

import countReducer from "./count"

import favoriteThingsReducer from "./favoriteThings"

import youTubeVideoReducer from "./youTubeVideo"

import userReducer from "./user"

const rootReducer = combineReducers({

count: countReducer,

favoriteThings: favoriteThingsReducer,
youTubeVideo: youTubeVideoReducer,

user: userReducer

})

const store = createStore(rootReducer)

store.subscribe(() => {

console.log(store.getState())

})

export default store

```

In general index.js:

```javascript

import store from "./redux"

import {changeCount} from "./redux/count"

import {addFavoriteThing, removeFavoriteThing} from "./redux/favoriteThings"

import {setYouTubeTitle, incrementViewCount, upvoteVideo, downvoteVideo} from


"./redux/youTubeVideo"

import {setUserDetails} from "./redux/user"

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

Making a counter app

```javascript

// redux/index.js

import redux, {createStore} from "redux"

function increment() {

return {

type: "INCREMENT"

function decrement() {

return {

type: "DECREMENT"

// 2. Create a reducer to handle your increment and decrement actions

function reducer(count = 0, action) {

switch(action.type) {

case "INCREMENT":

return count + 1

case "DECREMENT":

return count - 1
default:

return count

const store = createStore(reducer)

store.subscribe(() => console.log(store.getState()))

export defautl store

```

In react main index.js

There is a react-redux library that provide tools to integrate redux

```jsx

// index.js

import React from "react"

import ReactDOM from "react-dom"

import {Provider} from "react-redux"

import App from "./App"

import store from './index'

ReactDOM.render(

<Provider store={store}>

<App />

</Provider>,
document.getElementById("root")

```

## Redux in React - connect

* Higher-order component

* Pass 2 things:

1. What parts of the global state does this component wants access to?

2. what actions do you want to be able to dispatch from this component?

* 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("What parts of state do you want?",

"What actions do you want to dispatch?")(Component)

/* connect will return a function

that we call immediataly with our

component that we want to connect to redux*/

connect(mapStateToPropsFun,

mapDispatchToPropsFunc)(Component)

```

mapDispatchToPropsFunc tends to be an object

In a component:

```jsx

// App.jsx
import React from "react"

import {connect} from "react-redux"

function App(props) {

return (

<div>

<h1>COUNT GOES HERE</h1>

<button>-</button>

<button>+</button>

</div>

export default connect(/*

what parts of state do you want?,

what actions you want to dispatch? */)(App)

```

## mapStateToProps

```jsx

import React from "react"

import {connect} from "react-redux"

function App(props) {

return (

<div>

<h1>{props.count}</h1>

<button>-</button>

<button>+</button>
</div>

// Write the mapStateToProps function from scratch

// Takes the global state from Redux as a parameter

// 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 {

count: globalState // intially 0

export default connect(mapStateToProps, {})(App)

```

Intially it's 0 because in our counter we define that state is a number first 0

---

## mapDispatchToProps

What actions you want to be able to dispatch

```javascript

// redux/index.js

import redux, {createStore} from "redux"

export function increment() {

return {

type: "INCREMENT"
}

export function decrement() {

return {

type: "DECREMENT"

function reducer(count = 0, action) {

switch(action.type) {

case "INCREMENT":

return count + 1

case "DECREMENT":

return count - 1

default:

return count

const store = createStore(reducer)

store.subscribe(() => console.log(store.getState()))

export default store

```

```javascript

// App.js

import React from "react"

import {connect} from "react-redux"


import {increment, decrement} from "./redux"

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

export default connect(mapStateToProps, mapDispatchToProps)(App)

// or also

export default connect(state => ({count: state}), {increment, decrement})(App)

```
[React Documentation](https://react-redux.js.org/api/connect#connect)

We can improve this with hooks

## useSelector

```jsx

import React from "react"

import {useSelector} from "react-redux"

import {increment, decrement} from "./redux"

function App(props) {

const count = useSelector(state => state)

return (

<div>

<h1>{count}</h1>

<button onClick={props.decrement}>-</button>

<button onClick={props.increment}>+</button>

</div>

// export default connect(state => ({count: state}), {increment, decrement})(App)

export default App

```

## useDispatch

```javascript

import React from "react"

import {useSelector, useDispatch} from "react-redux"


import {increment, decrement} from "./redux"

function App(props) {

const count = useSelector(state => state)

const dispatch = useDispatch()

return (

<div>

<h1>{count}</h1>

<button onClick={() => dispatch(decrement())}>-</button>

<button onClick={() => dispatch(increment())}>+</button>

</div>

// export default connect(state => ({count: state}), {increment, decrement})(App)

export default App

```

For testing can be better to use connect

[Article](https://thoughtbot.com/blog/using-redux-with-react-hooks)

[Warnings about hooks](https://react-redux.js.org/api/hooks#usage-warnings)

---

## Redux Thunk

Allow us to have our actionCreators dealing asynchronous

In an API it won't wait to the api it will return

Thunk enables to return a function


```javascript

export function increment() {

// API call to get the current count

return (dispatch) => {

// Do all sorts of async stuff first,

// THEN use dispatch({type: "INCREMENT"})

return {

type: "INCREMENT"

```

install redux-thunk

thunk is a middle-ware, so I need to apply it to redux

```jsx

import redux, {createStore, applyMiddleware} from "redux"

import thunk from "redux-thunk"

export function increment() {

return (dispatch) => {

setTimeout(() => {

dispatch({type: "INCREMENT"})

}, 1500)
}

export function decrement() {

return {

type: "DECREMENT"

function reducer(count = 0, action) {

switch(action.type) {

case "INCREMENT":

return count + 1

case "DECREMENT":

return count - 1

default:

return count

const store = createStore(reducer, applyMiddleware(thunk))

store.subscribe(() => console.log(store.getState()))

export default store

```

Case of use

```javascript

import redux, {createStore, applyMiddleware} from "redux"

import thunk from "redux-thunk"


export function increment() {

return (dispatch, getState) => {

const number = getState()

const baseUrl = "https://swapi.co/api/people"

fetch(`baseUrl/${number}`)

.then(res => res.json())

.then(res => {

console.log(res)

dispatch({

type: "INCREMENT",

payload: res

})

})

export function decrement() {

return {

type: "DECREMENT"

function reducer(count = 0, action) {

switch(action.type) {

case "INCREMENT":

return count + 1

case "DECREMENT":

return count - 1
default:

return count

const store = createStore(reducer, applyMiddleware(thunk))

store.subscribe(() => console.log(store.getState()))

export default store

```

You might also like