Instructure Tech Blog

Torpedoes of nerdy truth from the engineering team at Instructure

Facebook React vs. Ember

| Comments

Edited Jan 13, 2014 with minor changes which allow for testing timeouts in Ember.

We engineers at Instructure love Ember. We also love to learn about other frameworks and know which tools are the best for the job at hand. One of the newer frameworks to come out is React.

React is Facebook’s new JavaScript library for building user interfaces. I’m excited to see another player in the game of front-end JS frameworks. When we have so much mind share going to a similar problem, we all win.

On the home page (screenshot), there are a few examples of how React can be used. I thought it might be fun to show how each of these can be done using Ember.

A Simple Component

React components take data and return content which is to be displayed. Here’s their example code:

React
1
2
3
4
5
6
7
8
/** @jsx React.DOM */
var HelloMessage = React.createClass({
  render: function() {
    return <div>{'Hello ' + this.props.name}</div>;
  }
});

React.renderComponent(<HelloMessage name="John" />, mountNode);

Let’s build this in Ember. For something as simple as this example, a Handlebars Helper will do the trick.

Ember
1
2
3
4
5
6
Ember.Handlebars.helper('hello-message', function(name) {
  var escaped = Handlebars.Utils.escapeExpression(name);
  return new Handlebars.SafeString('<div>Hello ' + escaped + '</div>');
});

{{{hello-message "John"}}}

Example:

Facebook React vs Ember

A Stateful Component

The stateful component example on the React page is cool. It tracks the elapsed time on a page (since refresh). Here’s their code:

React
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Timer = React.createClass({
  getInitialState: function() {
    return {secondsElapsed: 0};
  },
  tick: function() {
    this.setState({secondsElapsed: this.state.secondsElapsed + 1});
  },
  componentDidMount: function() {
    this.interval = setInterval(this.tick, 1000);
  },
  componentWillUnmount: function() {
    clearInterval(this.interval);
  },
  render: function() {
    return React.DOM.div({},
      'Seconds Elapsed: ' + this.state.secondsElapsed
    );
  }
});

Since we are maintaining state within the component itself, let’s build it using an Ember Component.

Ember
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
App.TimeOnPageComponent = Em.Component.extend({
  secondsViewed: 0,

  // Increment the secondsViewed attribute
  tick: function() {
    this.incrementProperty('secondsViewed');
  },

  onInterval: function(){
    Ember.run(this, this.tick);
  },

  // Initiates timer when element is rendered
  startTimer: function() {
    this._interval = setInterval(this.onInterval.bind(this), 1000);
  }.on('didInsertElement'),

  // Ensure that the timer stops when closed
  clearInterval: function() {
    clearInterval(this._interval);
    this._interval = null;
  }.on('willDestroyElement')
});

Example:

Facebook React vs Ember

An Application

The application example is a simple todo list which has a TodoList class (for rendering content) and a TodoApp class (maintains the state and renders the layout). Here’s the code:

React
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/** @jsx React.DOM */
var TodoList = React.createClass({
  render: function() {
    var createItem = function(itemText) {
      return <li>{itemText}</li>;
    };
    return <ul>{this.props.items.map(createItem)}</ul>;
  }
});
var TodoApp = React.createClass({
  getInitialState: function() {
    return {items: [], text: ''};
  },
  onChange: function(e) {
    this.setState({text: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var nextItems = this.state.items.concat([this.state.text]);
    var nextText = '';
    this.setState({items: nextItems, text: nextText});
  },
  render: function() {
    return (
      <div>
        <h3>TODO</h3>
        <TodoList items={this.state.items} />
        <form onSubmit={this.handleSubmit}>
          <input onChange={this.onChange} value={this.state.text} />
          <button>{'Add #' + (this.state.items.length + 1)}</button>
        </form>
      </div>
    );
  }
});
React.renderComponent(<TodoApp />, mountNode);

When writing this in Ember, we would use both a controller and a handlebars template. The controller will maintain the state and respond to user interaction and the template will display the form and list.

Ember (controller)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
App.ApplicationController = Em.ArrayController.extend({
  content: function() {
    return [];
  }.property(),

  btnLabel: function() {
    return "Add #" + (this.get('content.length') + 1);
  }.property('content.length'),

  actions: {
    handleSubmit: function() {
      this.get('content').pushObject(this.get('text'));
      this.set('text', '');
    }
  }
});
Ember (template)
1
2
3
4
5
6
7
8
9
10
11
12
13
<div>
  <h3>TODO</h3>
  <ul>
  {{#each content}}
    <li>{{this}}</li>
  {{/each}}
  </ul>

  <form {{action "handleSubmit" on="submit"}}>
    {{input value=text}}
    <button type="submit">{{btnLabel}}</button>
  </form>
</div>

Example:

Facebook React vs Ember

A Component Using External Plugins

This example on the React website under A Component Using External Plugins is a great example of encapsulating functionality.

React
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/** @jsx React.DOM */

var converter = new Showdown.converter();

var MarkdownEditor = React.createClass({
  getInitialState: function() {
    return {value: 'Type some *markdown* here!'};
  },
  handleChange: function() {
    this.setState({value: this.refs.textarea.getDOMNode().value});
  },
  render: function() {
    return (
      <div className="MarkdownEditor">
        <h3>Input</h3>
        <textarea
          onChange={this.handleChange}
          ref="textarea"
          defaultValue={this.state.value} />
        <h3>Output</h3>
        <div
          className="content"
          dangerouslySetInnerHTML={{
            __html: converter.makeHtml(this.state.value)
          }}
        />
      </div>
    );
  }
});

React.renderComponent(<MarkdownEditor />, mountNode);

The caveat to this example is that it assumes that the showdown library is already loaded. In our Ember example, note that the library is loaded in the head.

Ember (controller)
1
2
3
4
5
6
7
8
9
10
11
App.ApplicationRoute = Em.Route.extend({
  model: function() {
    return { value: 'Type some *markdown* here!' };
  }
});

Em.Handlebars.registerBoundHelper('markdown', function(str) {
  var converter = new Showdown.converter();
  var html = converter.makeHtml(str || '');
  return new Handlebars.SafeString(html);
});
Ember (template)
1
2
3
4
5
6
<div class="container">
  <h3>Input</h3>
  {{textarea value=value class="form-control"}}
  <h3>Output</h3>
  {{markdown value}}
</div>

I added Twitter Bootstrap to this example for cleanliness.

Facebook React vs Ember

I haven’t spent enough time with React to be able to convert the Ember examples to it, but I would love to see someone do that for a better comparison.

Eric Berry is a software engineer at Instructure and loves giving back to the community via blog posts and meetups. He was a co-founder of ShareAPhoto, Ltd, a European online photo sharing company which was acquired by Hewlett-Packard in 2006. He was the author of Rails Pocket Reference (published 2008 – O’Reilly). He spear-headed TeachMeToCode.com, a popular on-demand screencasting website dedicated to helping others learn how to program.

Comments