Redux and Redux toolkit
Overview of the redux pattern: We treat our app data or state as a global immutable object. The only way we can change the current state of the app is by dispatching actions (on say a button click for example) and the payload for that action will go to a reducer function to determine what the next state will look like. The store itself is an observable so everything will happen in a reactive way meaning anything that subscribes to the store will be updated with the latest data.
The benefits of this pattern is we have one way data flow that provides a history predictable of all the changes that occur in the app so we can do time-travel debugging.
I am a fan of Context and “useReducer” but many teams still use redux. Almost 60% of react projects are built with redux.
Watched Mosh Hamedami’s Intro on Youtube here. This is the first hour of his 6 hour course.
Based on Facebook’s Flux, Mobx is an alternative to redux.
Leverage a store
The redux dev tools chrome extension is a thing.
Cache or preserve page state, (sort’s hang around).
Easily debug.
Can use with multiple frontend frameworks
Cons: complexity and verbosity
You don’t need to use redux for every project.
Starter project has simple “webpack” setup
Redux is based on the “functional programming” paradigm
Small functions: more concise, easier to debug, easier to test, more scalable
Functions are first-class citizens, meaning we can store them in a variable, pass them as an argument to other functions
Higher order functions: function that takes a function as an argument, or return a function, or both.
Composing and piping. Use “lodash” to help with havign to read fomr right to left and unnecessary parenthesis:
compose(a, b, c)
but to prevent having to read form right to left use “pipe”
const transform = pipe(trim, toLowerCase, wrapInDiv)
Currying:
Technique that takes a function taking “n” arguments and converts it to having a single argument.
Instead of separate parameters with commas, we use parenthesis
function add(a) {
return function(b) {
return a + b
}
}
add(1)(5)
and with arrow functions:
const add2 = a => b => a + b
A pure function always returns the same value if passed the same parameter. NO random values, no current date/time, no global state (DOM, files, db, etc.)
Benefits of pure functions: self-documenting, easily testable, concurrency, cacheable
Immutability goes hand and hand with pure functions ()strings are immutable but objects and arrays are mutable)
Why immutability? predictability, faster change detection, concurrency
Cons to immutability: performance, memory overhead
Practice immutability with objects
Object.assign({}, person, {name: "Bob", age: 30})
A better way is to use the spread operator
const updated = {...person, name: "Bob"}
But these are shallow copies to you need to be careful.
In order to do deep copy:
const updated = {
...person,
address: {
...person.address,
city: "New York"
},
name: "Bob"
}
Very verbose which is why we have libraries to help with this
Adding and removing ad updating from array, (good examples)
JavaScript does not prevent object mutations because it is not a pure functional programming language. To get around this we have to work with libraries that offer real immutable data structures, (Immutable, Immer, and Mori).
Quick tour of immutable.js library shown. The package “immer” is preferred by the author and npmtrends.com
import {produce} from "immer"
What’s beautiful about immer is our code looks familiar to us but our data does not get mutated.
Store is the single source of truth. Cannot directly modify or mutate the store. So we need to write a function that takes the store as an argument and returns the updated store. Use the spread operator or one of the immutability libraries shown earlier.
This function is often called “reducer” and a lot of people gripe about this. how does it know what property to update? The function takes a second parameter that is called an action.
Can have a reducers for each slice of your store.
We dispatch actions.
Major steps: Design the store, define the actions, create a reducer, set up the store.
install redux
An “action” is just a plain JS object that describes what just happened.
Create the reducer. Can use if and else or a switch statement.
function reducer(state = [], action) {
if (action.type === "bugAdded") {
return [
...state,
{
description: action.payload.description,
resolved: false
}
]
}
else if (action.type === "bugRemoved") {
return state.filter(bug => bug.id !== action.payload.id)
}
return state
}
See the tutorial for the switch version
In redux the reducers have to be pure functions
create the store
import {createStore} from "redux"
We can only “getState” from the store. To set the state we need to dispatch an action.
subscribe and unsubscribe from the store.
subscribing:
unsubcribing:
The “dispatch()” method
Since we refer to the names of our action both when we define them and call them it is common practice to create a reference file in case the name is ever changed on the future we can update in one place.
Create an “ActionTypes.js” file
export const BUG_ADDED = "bugAdded"
Then import with asterisk and alias
import * as actions from "./actionTypes"
// reference like this:
actions.BUG_REMOVED
Action Creators: dispatching an action is not easy, we have to type the entire structure of the object. We can instead create a function that can create this object for us. Great again in case there are future changes we can update one place.
Exercise of adding a new feature of “resolving bugs”