How I learned to stop worrying and love React

If you asked me what I thought of React two months ago, I would probably say...

Where are my templates? What's that crazy HTML doing in my JavaScript? JSX looks weird! Hurry! Kill it with fire!

Kill it with fire

That was because I didn't get it.

But I swear: React is definitely on the right track... Please, hear me out.

Good old MVC

The Root of All Evil in an interactive application is managing state. The "traditional" approach is the MVC architecture, or some variation thereof.

MVC proposes that your Model is the single source of truth - all state lives there. Views are derived from the Model, and must be kept in sync. When the Model changes, so does the View.

Finally, user interactions are captured by the Controller, which updates the Model. So far, so good.

Render the view when the model changes

This looks quite simple. First, we need to describe our View - how it transforms the model state into DOM. Then, whenever the user acts we update the Model and re-render the entire thing... right? Not so fast. Unfortunately, this is not very straight forward for 2 reasons:

  1. The DOM actually has some state, like the content of a text input. If you re-render disregarding your DOM completely, this content will be lost.
  2. DOM operations (like removing and inserting nodes) are really slow. Constantly rendering everything will lead to terrible performance.

So how do we keep the Model and View in sync avoiding these problems?

Data binding

For the past 3 years, the most common framework feature introduced to solve this problem was data binding.

Data binding is the ability to keep your model and view in sync automatically. Usually, in JavaScript, that means your Objects and your DOM.

It achieves that by letting you declare the dependencies between the pieces of data in your app. Changes in the state are propagated throughout your application and all depending pieces are automagically updated.

Let's see how that works in practice with some famous frameworks.

Knockout

Knockout argues for the MVVM (Model-View-ViewModel) approach and helps you implement the "View" parts:

// View (a template)
<p>First name: <input data-bind="value: firstName" /></p>  
<p>Last name: <input data-bind="value: lastName" /></p>  
<h2>Hello, <span data-bind="text: fullName"> </span>!</h2>

// ViewModel (diplay data... and logic?)
var ViewModel = function(first, last) {  
  this.firstName = ko.observable(first);
  this.lastName = ko.observable(last);

  this.fullName = ko.pureComputed(function() {
      // Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName.
      return this.firstName() + " " + this.lastName();
  }, this);
};

And, voilĂ . Changing the value of either input will provoke a change in the span. You never wrote code to wire it up. How cool is that, huh?

But wait, what about the Model being the single source of truth? Where should this ViewModel get its state from? How does it know that the Model changed? Interesting questions.

Angular

Angular describes data binding in terms of keeping the Model and View in sync. From the docs:

But... should the View communicate with the Model directly? Are they that tightly coupled?

Anyway, let's look at the obligatory hello world example:

// View (a template) 
<div ng-controller="HelloController as hello">  
  <label>Name:</label>
  <input type="text" ng-model="hello.firstName">
  <input type="text" ng-model="hello.lastName">
  <h1>Hello {{hello.fullName()}}!</h1>
</div>

// Controller 
angular.module('helloApp', [])  
.controller('HelloController', function() {
  var hello = this;
  hello.fullName = function() {
    return hello.firstName + hello.lastName;
  };
});

From this example, it looks like the Controller has state and is behaving like a Model - or perhaps a ViewModel? Assuming the Model is elsewhere, how is it kept in sync with the Controller?

My head is starting to hurt a little.

The problems with data binding

Data binding works wonderfully for small examples. However, as your app grows you'll probably face some of these problems.

Declaring dependencies can quickly introduce loops

The most common problem is having to cope with the side effects of changes in your state. This image from the introduction of Flux explains quite clearly how dependency hell starts to creep in:

In this scenario, can you predict what changes will happen when one change happens to a single Model? It is very hard to reason about code that can be executed in a completely arbitrary order when any dependency changes.

Template and display logic are artificially separated

What's the role of a View? To present the data to the user. What's the role of a ViewModel? To present the data to the user. What's the difference? None!

Templates separate technologies, not concerns ~ Pete Hunt

In the end, a View component should be able to manipulate its data and present it in the desired format. However, all template languages are inherently crippled: they can never achieve the same expressiveness and power as code.

Quite simply, {{# each}}, ng-repeat and databind="foreach" are all poor replacements for something that is native and trivial in JavaScript: a for loop. And they can't go any step further. So no filter or map for you.

Data binding is a hack around re-rendering

The Holy Grail of simplicity is not in discussion. What everyone always wanted was to re-render our entire app when state changes. This way, we could stop having to deal with Root of All Evil problem: state changing over time - we could simply describe what our app looks like given any particular state.

So, the problem is clear. Man, I wish some giant company could spare a team of genius developers to really nail the solution to this problem...

Enter Facebook's React

Turns out they did. React implements a virtual DOM which kind of delivers us the Holy Grail.

What is virtual DOM anyway?

I'm glad you asked! Let's look at a trivial React example.

var Hello = React.createClass({  
    render: function() {
        return <div>Hello {this.props.name}</div>;
    }
});

React.render(<Hello name="World" />, document.getElementById('container'));  

That's all of the required API for a React component. You must have a render method. Complex, huh?

OK, but what about that <div>? That's not JavaScript! It sure isn't.

Your new friend, JSX

This code is actually written in JSX, a super-set of Javascript which includes that brackets syntax for defining components. The code above, when compiled into JavaScript, will actually become:

var Hello = React.createClass({displayName: "Hello",  
    render: function() {
        return React.createElement("div", null, "Hello ", this.props.name);
    }
});

React.render(React.createElement(Hello, {name: "World"}), document.getElementById('container'));  

Did you notice the calls to createElement? These objects compose the virtual DOM implementation.

Quite simply: React first assembles the entire structure of your app in-memory, using those objects. Then, it converts that structure into actual DOM nodes and inserts them in your browser's DOM.

OK, but what's the point of writing our HTML with those strange createElement functions?

Virtual DOM is FAST

As we already discussed, manipulating the DOM is ridiculously expensive, so it must be done as few times as possible.

React's virtual DOM, however, makes it really fast to compare two trees and find exactly what changed between them. That way, React is able to compute the minimum set of changes necessary to update the DOM.

Practically speaking, React can diff two DOM trees and discover the minimum set of operations it needs to perform. This means two things:

  1. If an input with text is re-rendered and React expects it to have that content, it won't touch the input. No more state loss!
  2. Diffing the virtual DOM is not expensive at all, so we can diff it as much as we like. When it's ready to actually alter the DOM, it will only perform the least possible number of operations. No more slow layout thrashing!

Remember the two problems with re-rendering our entire app when state changes?

Gone.

React maps state to DOM

Virtual DOM rendering and diffing is the only magical part about React. Its excellent performance, however, is what fundamentally enables us to have a much simpler architecture overall. How simple?

React components are idempotent functions. They describe your UI at any point in time, just like a server-rendered app. ~ Pete Hunt, React: Rethinking best practices

Idempotent-function-simple.

That's all a React component should be, really. It maps the current app state to DOM. And you have the full power of JavaScript to describe your UI - loops, functions, scope, composition, modules - not a crippled template language.

var CommentList = React.createClass({  
  render: function() {
    var commentNodes = this.props.data.map(function (comment) {
      return (
        <Comment author={comment.author}>
          {comment.text}
        </Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
});

var CommentBox = React.createClass({  
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
      </div>
    );
  }
});

React.render(  
  <CommentBox data={data} />,
  document.getElementById('content')
);

Start using React today

React can be a little daunting at first. It proposes a really large paradigm shift, which is always uncomfortable. However, the advantages become clear when you start using it.

The React documentation is excellent. You should give it a try and follow the tutorial. I'm sure you'll love it if you give it a chance.

Happy coding!

comments powered by Disqus