15 July 2016

Post-MVC part 5: Redux

Intro

Last week we delved into Flux and saw the benefits of having a unidirectional architecture. We also learned that there were multiple Flux implementations, each implementing Flux in slightly different ways to improve upon it.

This week I want to take a look at Redux which takes Flux and improves it greatly making all sorts of cool things possible. Redux is the most popular Flux implementation at the moment.

Redux

Redux takes Flux and improves on it, so what are the differences between Flux and Redux?

A single source of Truth

Flux has the concept of Stores, which represent a group of domain entities and the operations that can be performed on them. For example a CarStore would contain all 'car' objects and have operations to add and remove cars.

Each domain entity has their own Store. This means that the 'state' of the application, whilst having a clear location, is still located in multiple objects. Redux argues against this approach, Redux has a single Store for every entity. In other words the entire state of the application is stored inside a single variable.

This sounds a bit like a mad idea but it actually has benefits.

Do you know what this reminds me of... websites. Earlier in my career I interned as a PHP developer at a company that made websites. To make the websites more dynamic we used PHP in combination with MySQL as the database to store the state in.

The database was the single source of truth. Whatever data was in the database was the truth. The PHP layer simply took that data and transformed it into HTML for the browser to render.

Whenever something glitchey happened due to a loss of network connection or some JavaScript enhancement that did not work for some reason, we always told the customer to simply reload the 'page'. This would give them the representation of what was in the database at that moment. In other words the page would show the correct 'state' again. They may lose some data in the process but at least they knew the 'truth' again.

Having a single source of truth made debugging easier. If we saw some strange text we would check the database to see the actual 'truth'. If there was a mismatch the PHP must have done something with the value we did not expect. I would trace the value through the system until I found the culprit.

Redux brings back this workflow. See something strange on the page, check the 'truth' first and then figure out what the problem is.

A single source of truth on the client side also has some debugging benefits. Imagine sending the current state whenever a JavaScript error occurs, including a list of actions, back to the server. You can then locally on your dev machine, load the state for that user at that particular time and replay the actions. With a single source of truth this is trivial, imagine doing the same in an Angular 1.x application!

Having the state in one location also makes Redux conceptually easier. You simply know where the state is located in your application.

Dispatcher

Redux now longer has a Dispatcher. In Flux the Dispatcher, a singleton, would act as a conduit for all Actions, the stores would register themselves to the Dispatcher to be notified of when an Action occurred.

Since there is only a single Store in Redux the need for the Dispatcher as a separate object is no longer needed. The singular Store itself can take on this responsibility instead. So while there still a Dispatcher concept is has been merged into the Store.

Pure Reducers

Redux also has a very strict philosophy on how the state is changed. Redux advocates doing state change via pure functions. A pure function has the following characteristics:

A pure function does not mutate anything in the outside world. Whatever the state was outside of the function that state will be the same after the function was called. In other words external variables / objects / arrays will not be changed.

A pure function will always give the same output when the same input is given. In other words if you give a function the same parameters / arguments it will always return the same output. For example if we give a function the parameters 10 and 12 and returns 42. It should always return 42 when the parameters 10 and 12 are given.

These two characteristics of a pure function makes them very reliable and easy to test. These also make them very appropriate to handle state changes.

Redux has a single Store which can be modified by sending Actions to it. The Actions are processed by a single 'Reducer'. A reducer is  a function which takes the previous state and the Action and returns a completely new state. Redux's website describes the signature of a reducer as follows: (previousState, action) => newState.

Counter App Example

Let's look at a simple example. Lets say we have an application which counts how many laps a person walked around a track. The application is very simple it has two buttons one to increment the count and one to decrement the count for when a mistake was made.

Lets look at the store for such a state and its reducer:

// The initial count starts at zero.
const initialCount = 0;

// Defines all possible actions as const's.
const INCREMENT_COUNTER = "INCREMENT_COUNTER";
const DECREMENT_COUNTER = "DECREMENT_COUNTER";

// Create the store with the reducer and the initial state.
let store = Redux.createStore(counterApp, initialCount);

// The reducer for the counter application.
function counterApp(state, action) {
  switch(action.type) {
    case INCREMENT_COUNTER:
      return state + 1;
    case DECREMENT_COUNTER:
      return state - 1;
  } 

  return state;
};

/* Action creator functions: functions that create Redux actions */

function incrementCounter() {
  return {
    type: INCREMENT_COUNTER
  };
}

function decrementCounter() {
  return {
    type: DECREMENT_COUNTER
  };
}

The full example can be found here: http://codepen.io/anon/pen/oxqEpd?editors=0010

In the counter example the state was simply a number which was either incremented or decremented. Lets look at a more complex example: an application which has multiple counters, and has a total count for all counter combined.

// Initially there will be zero counters
const initialState = { counters: {}, nextCounterId: 0, totalCount: 0 };

// Define the actions for adding and removing counters.
const CREATE_COUNTER = "CREATE_COUNTER";
const REMOVE_COUNTER = "REMOVE_COUNTER";

// Defines the actions on a counter.
const INCREMENT_COUNTER = "INCREMENT_COUNTER";
const DECREMENT_COUNTER = "DECREMENT_COUNTER";

// Create the store with the reducer and the initial state.
let store = Redux.createStore(counterApp, initialState);

// The reducer for the multi-counters application.
function counterApp(state, action) {
  switch(action.type) {
    case CREATE_COUNTER:
      var nextState = Object.assign({}, state); // Makes a copy of the state.
      nextState.nextCounterId = state.nextCounterId + 1;
      nextState.counters[state.nextCounterId] = { 
        count: 0, 
        counter: state.nextCounterId 
      };
      return nextState;
    case REMOVE_COUNTER:
      var nextState = Object.assign({}, state); // Makes a copy of the state.
      delete nextState.counters[action.counter];
      nextState.totalCount = calculateTotalCount(_.values(nextState.counters));
      return nextState;
    case INCREMENT_COUNTER:
      var nextState = Object.assign({}, state); // Makes a copy of the state
      nextState.counters[action.counter].count += 1; 
      nextState.totalCount = calculateTotalCount(_.values(nextState.counters));
      return nextState;
    case DECREMENT_COUNTER:
      var nextState = Object.assign({}, state); // Makes a copy of the state
      nextState.counters[action.counter].count -= 1;
      nextState.totalCount = calculateTotalCount(_.values(nextState.counters));
      return nextState;
  } 

  return state;
};

// Function to calculate the total count
function calculateTotalCount(counters) {
  return counters.reduce((total, counter) => total + counter.count, 0);
}

/* Action creator functions: functions that create Redux actions */

function incrementCounter(counter) {
  return {
    type: INCREMENT_COUNTER,
    counter: counter
  };
}

function decrementCounter(counter) {
  return {
    type: DECREMENT_COUNTER,
    counter: counter
  };
}

function createCounter() {
  return {
    type: CREATE_COUNTER
  };
}

function removeCounter(counter) {
  return {
    type: REMOVE_COUNTER,
    counter: counter
  }
}

Note that the Object.assign is used to make a copy of the state object. Remember we never want to mutate the 'old' state directly since this is impure.

The full example can be found here: http://codepen.io/anon/pen/wGmjxx?editors=0010

Benefits of Redux

Since Redux is Flux-like all benefits of Flux are also the benefits of Redux. See last weeks post. This is not true vice versa:

The benefits of Redux are the benefits of having a singular store with one variable holding the entire state. This enables a lot of cool functionality.

Time Travel Debugging

One benefit is Time Travel Debugging, which means you can go back to to a previous state whenever you want, and forward again.

This is possible due to the nature of Redux's pure reducer functions. The state is always copied and never directly mutated, this means you can keep an array of the previous states. If you want to go back to a certain state from the array you can simply assign it to the current state.

There is a Chrome plugin which builds a debugger into the devtools: https://github.com/zalmoxisus/redux-devtools-extension. If you want to play with time travel debugging yourself make sure the plugin is installed and check out the examples.

Hot reloading

Another benefit is so called hot reloading.

Normally when you work on a Component you will have to reload the page  every time the Component is changed to see the code changes effects. Some development systems can automate this task by watching the file system for you.

Hot reloading goes one step further. Hot reloading will change the Component without reloading the page and playback all events to get the Component in the correct state. This means that you will see your code changes immediately reflected in your browser, without losing the state of the page.

See it in action here: http://gaearon.github.io/react-hot-loader/

Server Side Rendering

The third benefit of Redux's approach to state is that it is easier to do server side rendering via Universal JavaScript. Universal JavaScript is the idea that the same code you run on the browser can be run on a Node.js server as well.

The idea is simple: you use a Node.js server to run your JavaScript code  which generates the HTML, using the same code which normally runs on the browser. The HTML is sent over the network to the browser, the browser will then display the page as it normally does. The user will see the page instantly as soon as it is loaded, giving a better User Experience. Web crawlers such as Google and Bing can crawl the page too which helps your rankings.

The JavaScript is still also sent to the browser. It will start up and detect that there is already an initial state, as sent by the server. The JavaScript will then start up and make the page dynamic again. This process of 'attaching' to the HTML is called hydration.

The reason this is easy to do with Redux is because the state is in one single Store. Creating the HTML is a function of: `application(initialState) => HTML` regardless of architecture. In Redux to set the initial state you simply assign a single value to the Store, making this process easier.

Cons of using Redux

We are not in Kansas anymore

Redux has a weakness: it is not always easy to learn. Immutable,  Pure Functions, Reducers, Dispatchers, Thunks, etc etc.

While all of these things are not that difficult to learn, there are many of them. Thinking about Components and applications with these new concepts requires you to learn a new philosophy, and a different way of doing things.

The pay-off for learning these how ever is immense. They give you a completely new look at how state can be managed.

Immutability

The idea basic principle of Redux is relatively easy to learn. Each action produces a function which takes the state and returns a new state: `newState = action(oldState)`.

However their is one important caveat you cannot mutate the oldState instead you must always create an entire new state. Otherwise the goodies such as time travel debugging and hot reloading will not work. This means that you as a developer must be constantly aware of the fact that you must not mutate the state.

It would be really handy if variables in JavaScript were immutable. A variable is immutable if the state of that variable cannot be changed. The benefit of immutability is that you never have to be afraid of something changing variables value.

For example:

/* 
  Lets say we have an array of names and we want to print them
  to the console joined by a comma.
*/
var names = ["Kwik", "Kwek", "Kwak"];

/* 
  This function printArrayJoined comes from some external library. 
  We think this function just prints the names joined by a comma. 

  Unbeknown to us the printArrayJoined actually mutates the array!
  At the end of this function it will be empty.
*/
function printArrayJoined(array) {
  var out = '';

  var length = array.length;
  while(length !== 1) {
    out += array.shift() + ", ";

    length -= 1;
  }

  out += array.shift();

  console.log(out);
}

printArrayJoined(names); // prints "Kwik, Kwek, Kwak"

// At this point the names array is empty. Not what we expected.
console.log(names); // prints []

You can see it work here: http://codepen.io/anon/pen/aNxZex?editors=0011

The printArrayJoined mutated the array which was unexpected. Had the printArray been defined as:

function printArrayJoined(array) {
  var out =  array.join(','); 
  console.log(out);
}

Everything would be fine as 'join' is not a mutating function, it does not alter the array in any way. You can see it live here: http://codepen.io/anon/pen/GZLjKj?editors=0011

In languages such as Elm, which is a big inspiration for both React and Cycle.js, or Clojure immutability is built right into the language itself. In JavaScript this is not the case.

When writing Redux reducers you must be constantly aware of what you are writing to make sure you are not mutating state.

Redux has a great philosophy about how to manage state but unfortunately the language JavaScript does not make it easy to adhere to it. You will at times fight the language when writing reducers.

You could however use a library such as Immutable.js to get immutable collections. However it does come at the expense of having yet another abstraction.

Resources

The inventor of Redux: Dan Abramov has a great video showing of time travel debugging and Hot Reloading: https://www.youtube.com/watch?v=xsSnOQynTHs.

He also has a great free course on egghead about how to get started with Redux.

Reactive Programming

Now that we have seen Redux the most used Flux-like library it is time to look at a new architecture in the next weeks.

One of the criticisms about React is that it is not truly reactive, even though the name 'React' suggests it and that Reactive Programming would be a better model.

Next week we will take a look at Reactive Programming and see what the fuss is all about.

3 comments:

  1. Thanks for giving simple examples. I can understand the issue clearly mapquest driving directions You helped me much.

    ReplyDelete
  2. pg slot เกมแตกดีเยี่ยม เล่นเกมสล็อตสมรรถนะสูง สัมผัสการเล่นสล็อตแตกหนัก แตกจริง ที่มีความล้ำยุค เปิดให้เข้ามาเล่นเกมพนันที่ยอดเยี่ยม ค่ายเกมใหม่ที่มีคุณภาพ pg slot เกมแตกดีเยี่ยม นับได้ว่าเป็นอีก

    ReplyDelete