Redux

&

Reduxsauce

Redux!

What is it?

"A predictable state container for JavaScript apps"

Redux is a library to keep data organized and consistent across an app

In other words,

But how does it work?

Illustrative Story Time!

Depositing $$$

1. "I would like to deposit my wads of cash, please"

2. "Great, I can definitely help you!"

3. "Balance: 1 Billion Dollars"

=> We make a request to the teller

=> The teller accepts our cash, counts it, loads it, etc

=> Our bank account reflects any new changes

$$$ in the bank

=

Data in the app

3 Main Components of Redux

Action - The request made to the teller

Reducer - The teller who interprets our request & follows the proper procedure

Store - Bank vault where all money is stored

Things to note:

 

  • A law-abiding customer cannot go straight to the vault and take money => Data in the Store cannot be directly changed

 

  • A customer must make a request that the teller understands => Only specific actions can be properly interpreted

 

  • In this story, there is only one single bank in the entire world => There is only one store in an app

Redux manages app state, but...

what is State???

  • A Plain-Old-Javascript-Object (POJO) that holds our app's data
{
  user: {
    email: "pj@smartlogic.io",
    username: "thepeej",
    groupIds: [1, 5, 48]
  },
  firstTimeLoaded: false,
}
{ balance: 1000000000 }

Our story:

Slightly more realistic:

Action

The customer's request

  • A POJO that expresses user's intent
  • Has a required attribute of "type"
{ type: "WITHDRAW_TWENTY" }
{
  type: "DEPOSIT_MONEY",
  payload: 1000000000
}

Reducer

The bank teller

  • A pure JS function
  • Accepts two parameters - State & Action
  • Sets initial state
  • Always returns a new State object
function bankTransactions(state = { balance: 100 }, action) {
  switch (action.type) {
    case 'DEPOSIT_MONEY' :
      return { ... state, balance: state.balance + action.payload}
    case 'WITHDRAW_TWENTY' :
      return {... state, balance: state.balance - 20}
    default :
      return state
  }
}

Problem!

A reducer only returns a new state!

let state

function bankTransactions(state = {balance: 100}, action) {
  switch (action.type) {
    case 'DEPOSIT_MONEY' :
      return {...state, balance: state.balance + action.payload}
    case 'WITHDRAW_TWENTY' :
      return {... state, balance: state.balance - 20}
    default :
      return state
  }
}

bankTransactions(state, {type: "WITHDRAW_TWENTY"})
// => { balance: 80 }

bankTransactions(state, {type: "WITHDRAW_TWENTY"})
// => { balance: 80 }

bankTransactions(state, {type: "WITHDRAW_TWENTY"})
// => { balance: 80 }

Store

The Bank Vault

Ties everything together

  • Declares and encapsulates application state
  • getState(): Provides access to application state
  • dispatch(): "Saves" state changes from action/reducer output
  • There should be only one Store in an application

Dispatch

  • The only way to save state changes
let state

bankTransactions(state, {type: "WITHDRAW_TWENTY"})
// => { balance: 80 }

bankTransactions(state, {type: "WITHDRAW_TWENTY"})
// => { balance: 80 }

bankTransactions(state, {type: "WITHDRAW_TWENTY"})
// => { balance: 80 }

Problem:

function dispatch(action) {
  state = bankTransactions(state, action)
}

Solution:

dispatch({type: "WITHDRAW_TWENTY"})
// => { balance: 80 }

dispatch({type: "WITHDRAW_TWENTY"})
// => { balance: 60 }

dispatch({type: "WITHDRAW_TWENTY"})
// => { balance: 40 }

createStore

function createStore(reducer) {
















}
  let state
  function getState() {
    return state
  }
  return {
    dispatch,
    getState
  }
  function dispatch(action) {
    state = reducer(state, action)
  }
  dispatch({})
  • Requires a reducer to be passed in

( subscribe() & listeners are omitted in example )

createStore

let store = createStore(bankTransactions)

store.getState()
// => { balance: 100 }

store.dispatch({type: 'DEPOSIT_CASH', payload: 40})

store.getState()
// => { balance: 120 }
store.dispatch({type: 'WITHDRAW_TWENTY'})

store.getState()
// => { balance: 80 }

How do we use it?

Reduxsauce

Now onto

(This is a terrible logo)

Why use it?

  • Keeps Redux code well organized
  • Makes code easier to read and maintain
  • Allows for cleaner code expandability
  • Provides easier testing of Reducers

By what means?

  • createReducer()
  • createActions()

createReducer

import { createReducer } from 'reduxsauce'

// the initial state of this reducer
const INITIAL_STATE = { balance: 100 }

const depositCash = (state, action) => {
  return {... state, balance: state.balance + action.payload}
}
const withdrawTwenty = (state, action) => {
  return {... state, balance: state.balance - 20}
}

// map our action types to our reducer functions
const HANDLERS = {
  ['DEPOSIT_CASH']: depositCash,
  ['WITHDRAW_TWENTY']: withdrawTwenty
}

export default createReducer(INITIAL_STATE, HANDLERS)
import { createReducer } from 'reduxsauce'

const INITIAL_STATE = { balance: 100 }

const depositCash = (state, action) => {
  return {... state, balance: state.balance + action.payload}
}
export const withdrawTwenty = (state, action) => {
  return {... state, balance: state.balance - 20}
}

const HANDLERS = {
  ['DEPOSIT_CASH']: depositCash,
  ['WITHDRAW_TWENTY']: withdrawTwenty
}

export default createReducer(INITIAL_STATE, HANDLERS)

Initial state set outside of function definition

State manipulation organized into separate functions

Concise routing of action types to business logic

function bankTransactions(state = { balance: 100 }, action) {
  switch (action.type) {
    case 'DEPOSIT_MONEY' :
      return { ... state, balance: state.balance + action.payload}
    case 'WITHDRAW_TWENTY' :
      return {... state, balance: state.balance - 20}
    default :
      return state
  }
}

Original Reducer

Eliminates use of switch/case statement which can become messy

createActions

import { createActions } from 'reduxsauce'

const { Types, Creators } = createActions({
  withdrawTwenty: null,
  depositCash: ['payload']
})

Keys of object passed in will become keys/values of the Types after being converted to SCREAMING_SNAKE_CASE

Types
// => { DEPOSIT_CASH: "DEPOSIT_CASH", WITHDRAW_TWENTY: "WITHDRAW_TWENTY" }
function depositMoney(amount) {
  return { type: 'DEPOSIT_MONEY', payload: amount }
}
depositMoney(50)
// => { type: "DEPOSIT_MONEY", payload: 50 }

Now, let's talk about

action creators

A function that returns ('creates') an action

createActions

import { createActions } from 'reduxsauce'

const { Types, Creators } = createActions({
  withdrawTwenty: null,
  depositMoney: ['payload']
})
Creators.withdrawTwenty()
// => {type: "WITHDRAW_TWENTY"}

Creators.depositMoney()
// => {type: "DEPOSIT_MONEY"}

The keys of the object are also used as the keys (as-is) of Creators. The value is a function which returns an action (aka an "action creator")

(Remember, an action is a POJO with at least a type attribute.)

Creators.depositMoney(30)
// => {type: "DEPOSIT_MONEY", payload: 30}

Creators.depositMoney(30, 50)
// => {type: "DEPOSIT_MONEY", payload: 30}

Creators.withdrawTwenty(30)
// => {type: "WITHDRAW_TWENTY"}

strings become additional action attribute

Putting it all together

import { createReducer, createActions } from 'reduxsauce'

const { Types, Creators } = createActions({
  withdrawTwenty: null,
  depositCash: ['payload']
})

export default Creators

const INITIAL_STATE = { balance: 100 }

const depositCash = (state = INITIAL_STATE, action) => {
  return {... state, balance: state.balance + action.payload}
}

const withdrawTwenty = (state = INITIAL_STATE, action) => {
  return {... state, balance: state.balance - 20}
}

const HANDLERS = {
  [Types.DEPOSIT_CASH]: depositCash,
  [Types.WITHDRAW_TWENTY]: withdrawTwenty
}

export const reducer = createReducer(INITIAL_STATE, HANDLERS)
  • Creators are exported for use within mapDispatchToProps()

  • Reducer is exported for use within createStore()

And that's how we use reduxsauce!

It's reduxulously easy

Questions?