Notes

Redux Explained

pic
pic

The Anatomy of Redux

Where did Redux come from?

When is it appropriate to use Redux?

Vocabulary


Flux and Redux

What is Flux?

graph
graph

Redux

rdx
rdx

Store

Updating the Store

store.dispatch(action);

// Add Orange Action
const addOrange = {
  type: "ADD_FRUIT",
  fruit: "orange",
};

// Reducer for Orange Action
const fruitReducer = (state = [], action) => {
  switch (action.type) {
    case "ADD_FRUIT":
      return [...state, action.fruit];
    default:
      return state;
  }
};

// Run the Dispatch
console.log(store.getState()); // []
store.dispatch(addOrange);
console.log(store.getState()); // [ 'orange' ]

Subscribing to the store

const display = () => {
  console.log(store.getState());
};

const unsubscribeDisplay = store.subscribe(display);

store.dispatch(addOrange); // [ 'orange', 'orange' ]

// display will no longer be invoked after store.dispatch()
unsubscribeDisplay();

store.dispatch(addOrange); // no output

Reviewing a simple example

// app.js

const { createStore } = require("redux");

// Define the store's reducer.
const fruitReducer = (state = [], action) => {
  switch (action.type) {
    case "ADD_FRUIT":
      return [...state, action.fruit];
    default:
      return state;
  }
};

// Create the store.
const store = createStore(fruitReducer);

// Define an 'ADD_FRUIT' action for adding an orange to the store.
const addOrange = {
  type: "ADD_FRUIT",
  fruit: "orange",
};

// Log to the console the store's state before and after
// dispatching the 'ADD_FRUIT' action.
console.log(store.getState()); // []
store.dispatch(addOrange);
console.log(store.getState()); // [ 'orange' ]

// Define and register a callback to listen for store updates
// and console log the store's state.
const display = () => {
  console.log(store.getState());
};
const unsubscribeDisplay = store.subscribe(display);

// Dispatch the 'ADD_FRUIT' action. This time the `display` callback
// will be called by the store when its state is updated.
store.dispatch(addOrange); // [ 'orange', 'orange' ]

// Unsubscribe the `display` callback to stop listening for store updates.
unsubscribeDisplay();

// Dispatch the 'ADD_FRUIT' action one more time
// to confirm that the `display` method won't be called
// when the store state is updated.
store.dispatch(addOrange); // no output

Reducers

const fruitReducer = (state = [], action) => {
  switch (action.type) {
    case "ADD_FRUIT":
      return [...state, action.fruit];
    case "ADD_FRUITS":
      return [...state, ...action.fruits];
    case "SELL_FRUIT":
      const index = state.indexOf(action.fruit);
      if (index !== -1) {
        // remove first instance of action.fruit
        return [...state.slice(0, index), ...state.slice(index + 1)];
      }
      return state; // if action.fruit is not in state, return previous state
    case "SELL_OUT":
      return [];
    default:
      return state;
  }
};

Reviewing how Array#slice works

const fruits = ["apple", "apple", "orange", "banana", "watermelon"];

// The index of the 'orange' element is 2.
const index = fruits.indexOf("orange");

// `...fruits.slice(0, index)` returns the array ['apple', 'apple']
// `...fruits.slice(index + 1)` returns the array ['banana', 'watermelon']
// The spread syntax combines the two array slices into the array
// ['apple', 'apple', 'banana', 'watermelon']
const newFruits = [...fruits.slice(0, index), ...fruits.slice(index + 1)];

Avoiding state mutations

GOOD

const goodReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case "INCREMENT_COUNTER":
      const nextState = Object.assign({}, state);
      nextState.count++;
      return nextState;
    default:
      return state;
  }
};

BAD

const badReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case "INCREMENT_COUNTER":
      state.count++;
      return state;
    default:
      return state;
  }
};

Actions

Using action creators

const addOrange = {
  type: "ADD_FRUIT",
  fruit: "orange",
};

store.dispatch(addOrange);
console.log(store.getState()); // [ 'orange' ]
const addFruit = (fruit) => ({
  type: "ADD_FRUIT",
  fruit,
});
store.dispatch(addFruit("apple"));
store.dispatch(addFruit("strawberry"));
store.dispatch(addFruit("lychee"));
console.log(store.getState()); // [ 'orange', 'apple', 'strawberry', 'lychee' ]

Preventing typos in action type string literals

const ADD_FRUIT = "ADD_FRUIT";
const ADD_FRUITS = "ADD_FRUITS";
const SELL_FRUIT = "SELL_FRUIT";
const SELL_OUT = "SELL_OUT";

const addFruit = (fruit) => ({
  type: ADD_FRUIT,
  fruit,
});

const addFruits = (fruits) => ({
  type: ADD_FRUITS,
  fruits,
});

const sellFruit = (fruit) => ({
  type: SELL_FRUIT,
  fruit,
});

const sellOut = () => ({
  type: SELL_OUT,
});

Debugging Arrow Functions

Understanding the limitations of implicit return values

const addFruit = (fruit) => {
  return {
    type: "ADD_FRUIT",
    fruit,
  };
};

const addFruit = (fruit) => {
  debugger;
  return {
    type: "ADD_FRUIT",
    fruit,
  };
};