Redux

Predictable State Container

more on that in just a sec

Runs Everywhere

JavaScript does (browser, server, native)

View Library Agnostic

though often found with React

Excellent Docs & Videos

I'm totally plagiarizing rn

Packs a Punch

at ~ 2kB

Predictable State Container

Predictable

State

Container

Not a Database

not managed by redux

SELECT * FROM `player`
WHERE `name` LIKE 'Jared A%'

Not Server Session "State"

not managed by redux

HTTP/1.0 200 OK
Set-Cookie: LSID=DQAAAK…Eaem_vYg; Path=/accounts; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly
Set-Cookie: HSID=AYQEVn….DKrdst; Domain=.foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; HttpOnly
Set-Cookie: SSID=Ap4P….GTEq; Domain=foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly

Not Component Internal State

not managed by redux

like :hover or :active state

What Then?

Application State

  • in memory data assignment (eg: var, let, const)
  • that has app significance
  • is referenced asynchronously
  • and may change asynchronously
const scores = {
    Jared: Infinity,
    Bruce: 1,
    Matt: NaN
};

Application State

not app state

  • drop-down is open
  • what's being hovered over

is app state

  • list of options
  • chosen option

example

Application State

may consist of:

  • transformed server responses
  • cached data
  • locally created data
  • complex UI state
    • active route
    • the active tab
    • whether to show a spinner
    • whether to show pagination
    • alert messages / notifications

Predictable

State Container

If a model can update another model, then a view can update a model, which updates another model, and this, in turn, might cause another view to update. At some point, you no longer understand what happens in your app as you have lost control over the when, why, and how of its state. When a system is opaque and non-deterministic, it’s hard to reproduce bugs or add new features.

Managing State is Hard

http://redux.js.org/docs/introduction/Motivation.html

Opaque?

stuff is happening and you can't even

Non-deterministic?

the same set of actions produce different results

Insanity: doing the same thing over and over again and expecting different results.

add asynchronous behavior

and not in the good way

How is Redux Different?

Same Input Produces Same Output

Any Potential State Change is Clearly Declared Beforehand

Predictable State Container

We've Demonstrated some of the Complexities of State Management

So Obviously the Smart Thing to do is Let State Run Freely and Loose

When application state is decentralized and spread across your entire app - often hidden with behaviors, in functions, in closures, and/or bunch of global objects - it becomes even more impossible to manage as your app scales

Uncontained App State

class ScoreCard {

    constructor(name, initialScore=0) {
        this.name = name;
        this.score = initialScore;
    }

    add(number) {
        this.score += number;
    }
}

In Redux, your whole app state is stored separately -- contained in an object tree inside a single store.

There is one way in

There is one way out

Predictability

Clarity

Manageability

Sanity

3 Principles

of Redux

http://redux.js.org/docs/introduction/ThreePrinciples.html

Single Source of Truth

The state of your whole application is stored in an object tree within a single store.

State is Read-Only

The only way to mutate the state is to emit an action, an object describing what happened.

Changes via Pure Functions

emitted actions (along with the current state) are passed to pure functions which return a new state

Super Simple API

Redux has a

Super Simple API

import {createStore} from 'redux';
// you import your reducers
// you spend most of your time writing reducers
// more on that in a minute...
import myReducer from './my/reducer.js';
// you create the redux store from reducer(s)
export const store = createStore(myReducer);
// you get current state at any time by...
store.getState();
// you subscribe to be notified when things change
store.subscribe( () =>
  console.log(store.getState())
);
// dispatch "actions" asking store to change state...
// actions are just POJOS with a `type` and optional data
store.dispatch({type: "INCREMENT"});

Reducers

Reducers

(previousState, action) => newState
  • a regular JavaScript Function
  • that's a pure function
  • accepts previous state and action
  • returns a new state
  • returns the previous state for any unknown action.

Reducers

Things you should never do inside a reducer:

  • Mutate its arguments
  • Perform side effects
    • API calls
    • routing transitions
  • Call non-pure functions
    • Date.now()
    • Math.random().

Given the same arguments, it should

 

calculate the next state and return it.

 

No surprises. No side effects. No API calls. No mutations.

 

Just a calculation

Example Reducer

export default (state = 0, {type}) => {
    switch(type) {
        case 'INCREMENT':
            return state + 1;
	  
        case 'DECREMENT': 
            return state - 1;
	  
        default:
            return state;
  
    }
}

Did we:

  • mutate any args?
  • perform side effects?
  • call non-pure functions?
  • return a new state?

Redux Visualized

{type}

redux internal state tree

{type}
(state,action)
 => newState

reducer

Live Coding

import {createStore} from 'redux';

const myReducer = (state = 0, {type}) => {
  switch(type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(myReducer);

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

store.dispatch({type: "INCREMENT"});
store.dispatch({type: "DECREMENT"});

Answer

Actions & Action Creators

Actions

  • payloads of information that send data from your application to your store
  • are POJOs
  • must have a unique type property
  • may be accompanied by minimal data
const action = { type: 'ADD', number: 10 };

dispatch(action);

Action Creators

  • functions that create actions
  • abstract store from app
const add = (number) =>
    ({ type: 'ADD', number });

dispatch( add(10) );

don't write these

write these

Live Coding

RoadTrip Dad

add 10...

divide 2...

multiply 5...

multiply 4...

divide 10...

minus 1...

divide 3...

...what do we have?

start 0...

/* Task #1 */
const roadTripDad = 
   (state = 0, {type, number}) => {
    switch(type) {
	case 'START':
            return number;
	case 'ADD':
	    return state + number;
	case 'SUBTRACT':
	    return state - number;
	case 'MULTIPLY':
	    return state * number;
	case 'DIVIDE':
	    return state / number;
	default:
	    return state;
    }
}
  
/* Task #2 */
const start = number =>
  ({type: 'START', number});

const add = number =>
  ({type: 'ADD', number});

const subtract = number =>
  ({type: 'SUBTRACT', number});

const multiply = number =>
  ({type: 'MULTIPLY', number});

const divide = number =>
  ({type: 'DIVIDE', number});
/* Task #3 */
const store = createStore(roadTripDad);
store.subscribe(() => {
    render(
	store.getState(),
	number =>
            store.dispatch(add(number)),
	number =>
            store.dispatch(subtract(number)),
	number =>
            store.dispatch(multiply(number)),
	number =>
            store.dispatch(divide(number))
    )
});


/* Task #4 */
store.dispatch(start(10))

Combine Reducers

So, the entire app's state is contained in one reducer?

No

Redux Shines at Scale

Combine Reducers

// import createStore AND combineReducers
import {createStore, combineReducers} from 'redux';
// import all your apps' reducers
import talks from '../reducers/talks.js';
import speakers from '../reducers/speakers.js';
import sessions from '../reducers/sessions.js';
// create the state tree (aka: root reducer)
const rootReducer = combineReducers({talks, speakers, sessions});
// create the redux store from reducer(s)
export const store = createStore(rootReducer);
console.log( store.getState() );
/*  {talks: ..., speakers: ..., sessions: ...}; */

Visualized

Combine Reducers

{type}

redux internal state tree

{type}
(state,action)
 => newState
(state,action)
 => newState
{type}

reducer

reducer

Async Behavior

things you should never do inside a reducer

API Calls

Doing So Makes them Non-Deterministic, Unpredictable & Opaque

Where is Async Managed?

...

Live Coding

Answer

Middleware

a third-party extension point between dispatching an action, and the moment it reaches the reducer.

Visualized

Middleware

{type}

redux internal state tree

{type}
(state,action)
 => newState
()=>{}

middleware

reducer

What Does That Mean?

  • you write reducers the exact same way
  • you can intercept & act on actions coming in
  • you still dispatch regular POJOs
  • you can also dispatch in other things
    • thunks
    • promises
    • whatever the middleware supports

What It Looks Like

import { createStore, combineReducers, applyMiddleware } from 'redux';
import reducers from '../path/to/reducers.js';

// middleware that lets you dispatch thunks
import thunk from 'redux-thunk';

const rootReducer = combineReducers(reducers);

export const store = createStore(
    rootReducer, 
    applyMiddleware(thunk)
);

Using Redux Thunks

// Meet thunks.
// A thunk is a function that returns a function.
// This is a thunk.

function makeASandwichWithSecretSauce(forPerson) {

  // Invert control!
  // Return a function that accepts `dispatch` so we can dispatch later.
  // Thunk middleware knows how to turn thunk async actions into actions.

  return function (dispatch) {
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
    );
  };
}
store.dispatch(
  makeASandwichWithSecretSauce('My wife')
)

Redux

By Jared Anderson

Redux

  • 2,134