Alex's Notes

Redux Store

As described in Redux: Tyler McGinnis

If you boil down an app to its most fundamental components you get the state and UI.

Most bugs come down to state mismanagement.

By improving our state management we shall increase the predictability of our state, and decrease the amount of bugs in our applications.

The aim of the course is to make state more predictable.

Typically when we build an app we spread the state out across the application. In React world, all the state is spread out across the components.

How can we improve this to improve predictability? Instead of having the state spread across the application, we can centralize it into one location.

We’ll call this the state tree. Instead of the state living across the app, it will live in the tree.

What are the benefits of this?

First, a shared cache. We no longer have to lift state up to the shared parent (which could be several levels up).

Second, we get predictable state changes. We can set rules about how the state can be updated, and who can update it.

Third, improved developer tooling. Parts of the application can be reloaded without throwing away state.

Fourth, pure functions - having the state in one place means that our UI components can be pure, they just receive that state and render it. This makes the app more predictable.

Finally, server rendering. Here we grab the state and spit back the state and UI on the server to the client. This is easier if that state is in one place.

So what operations do we need on the state tree?

A get operation - to get state from the tree.

A listen operation - to receive a notification when the state changes.

An update operation - to update the state living in the tree.

Collectively - the tree and the three operations - will be called the store.

To increase predictability we also want a collection of possible events that can occur in oru application which will update the state.

In order to represent them we can use a plain JS object, which we’ll call ‘actions’.

Actions are payloads of information that send an instruction on what type of transformation to make to your application state as well as other relevant information.

An action itself might look like:

const action = {
    type: "LIKE_TWEET",
    id: 1939302013131,
    uid: "alex"
}

Note that only the ‘type’ is really mandatory, the rest depends on the information needed to update the state based on the action.

When a user first invokes our store, they will define a reducer function, that takes the current state, an action, and returns a new state. Note that this reducer should be pure, ie should guarantee the same output for the same input, depend on nothing but its input parameters, and cause no side effects.


// Reducer function
function todos(state = [], action) {
  switch(action.type) {
    case 'ADD_TODO' :
      return state.concat([action.todo])
    case 'REMOVE_TODO' :
      return state.filter((todo) => todo.id !== action.id )
    case 'TOGGLE_TODO' :
      return state.map( (todo) => todo.id !== action.id ? todo :
	Object.assign({}, todo, {complete: !todo.complete}))
    default :
      return state;
  }
}

// Another reducer
function goals(state = [], action) {
  switch(action.type) {
    case 'ADD_GOAL' :
      state.concat([action.goal])
    case 'REMOVE_GOAL':
      state.filter((goal) => goal.id !== action.id)
    default :
      return state
  }
}

//root reducer
function app(state = {}, action) {
  return {
    todos: todos(state.todos, action),
    goals: todos(state.goals, action),
  }
}

function createStore(reducer) {
  //The store will have four parts

  //1. the state
  let state;

  //2. Get the state
  const getState = () => state

  //3. Listen to changes
  let listeners = []
  const subscribe = (listener) => {
    listeners.push(listener);

    // return an unsubscribe function
    return () => {
      listeners = listeners.filter((l) => l !== listener)
    }
  }

  //4. Update the state
  const dispatch = (action) = {

    state = reducer(state, action);

    listeners.forEach((listener) => listener());
  }

  return {
    getState,
    subscribe,
    dispatch
  }
}

It’s a common pattern to use action creators like this:

function addTodoAction(todo) {

  return {
    type: 'ADD_TODO',
    todo
  }
}

store.dispatch(addTodoAction(todo));

Links to this note