React vs. Ember: Prelude

As with any prelude, this one isn’t required reading. Feel free to skip to React vs Ember: Event Handling.

This post is the first in a series that aims to objectively discuss some differences and similarities between how React-based projects and Ember projects solve the same problems.

The comparison will use an approach within both ecosystems that I think provides the best developer experience and ergonomics, with a focus on maintainability, ease of collaboration in large teams via TypeScript, and recommended architectural patterns that best fit the ecosystem.

This series does not offer a subjective comparison of why an individual or company should choose React (and its ecosystem) over Ember or vice-versa. Some major differences make React difficult to compare to Ember. Namely, React is a single library, and Ember is a framework consisting of multiple libraries.

This series assumes you have some general single-page-app and modern ECMAScript knowledge so that the content can focus on the problem being solved for that particular blog post. Each post will include (and eventually embed) runnable code samples to demonstrate the implementations in the grander scheme of an app.

Notes and caveats

Although my goal is to remain totally objective in this comparison of React (and its ecosystem) and Ember, some opinions are required to make the comparison easier. Many of these opinionated implementation details won’t matter for the content of the blog series, but will be present in the linked example code. A separate post down the road will discuss project structure in various scenarios.

 

React vs Ember: Event Handling

Because this post is the first comparison in the series, starting with something common across all frontend applications seems appropriate: event handling. Handling events is a must for handling user interaction and can quickly become tedious.

For the first demonstration of solving the same problem with both React and Ember, we will have a text box with an “on change” hook attached so that any changes to the content of the text box will be represented in the component’s internal state via an action.

The goal of this comparison is to demonstrate the similarities between the two toolsets and what developer ergonomics each has to offer.

Event Handling in React

The following is an example of how an input with an onChange handler is configured — along with some inline commentary.

src/ui/my-component.tsx

import * as React from 'react';

export interface State { textProperty?: string; }
export interface Props {}

export default class MyComponent extends React.Component<Props, State> {
  // React stores all local state in a class property.
  state: State = {};

  // Actions in React must have a binding to the class instance. Otherwise
  // `this.setState` is undefined because `this` isn't the instance.
  // There are a couple of ways around the error using `.bind`,
  // but this technique is the most concise.
  //
  // It works due to how class properties are transpiled --
  // by moving the definition inside the class constructor, which
  // auto-binds to the correct `this`.
  didChangeTextField = (event: React.ChangeEvent<HTMLInputElement>) => {
    const text = event.target.value;

    // React uses setState for asynchronously updating the state.
    // When the state is updated, a re-render of `this` and child components
    // will occur.
    // Note that setting any properties on the class itself will not cause a re-render.
    this.setState({ textProperty: text });
  }

  render() {
    const { textProperty } = this.state;

    return (
      <div>
        textProperty: {textProperty}<br />
        {/*
          In React, inputs must always have a set value if they are to be
          controlled by JavaScript code. In this case, since textProperty
          has no value without the `|| ''`, the input would initially
          be uncontrolled, and switch to controlled upon the update of
          the value of textProperty.

          For more information on uncontrolled components, see the 
            [React Uncontrolled Components Documentation](https://reactjs.org/docs/uncontrolled-components.html)

          The `onChange` here is part of a built-in component provided as part of jsx.
          See the [React Controlled Components Documentation](https://reactjs.org/docs/forms.html#controlled-components)
          It combines a few lower level DOM events such as keyup and paste for convenience.
        */}
        <input
          value={textProperty || ''}
          onChange={this.didChangeTextField} />
      </div>
    );
  }
}

With many event-handling functions, the component size can grow considerably. It may be tempting to do inline change-handling functions such as:

<input
  value={textProperty || ''}
  onChange={event => this.setState({ textProperty: event.target.value })} />

But the best practice is to keep templates as simple as possible and pull logic and functions out of them.

Event Handling in Ember

Mimicking what was done for React (above), the following component creates an event-handling action.

src/ui/components/onchange-action/{ component.ts | template.hbs }

import Component from '@ember/component';
import { action } from '@ember-decorators/object';

export default class MyComponent extends Component {
  // In Ember, component-level state is managed with class properties
  textProperty?: string;

  // An action in Ember is a function that is intended to be be
  // used within templates.
  // The `action` decorator here sets the function within an underlying 
  // `actions` object on the component. Within that `actions` object,
  // the templates know what actions are defined or not 
  // to differentiate between a helper function that may be present in a component.
  //
  // Note that you could also use a class property function, like that used 
  // in the earlier React example.
  // The only difference in the template would be that
  // `(action 'didChangeTextField')` becomes `(action didChangeTextField)`.
  //
  // However, actions in components are placed in an `actions`
  // object because `@ember/component` has a fairly large
  // public API. When actions are idiomatically defined in the
  // `actions` object, you remove the chance of naming collisions.
  // For example, if someone wanted to declare a destroy action as
  // `destroy = () => { /* behavior */ }`, that would break the
  // rendering process because the component couldn't be destroyed.
  //
  // For more information on the component api, see the
  //   [Ember Component API Documentation](https://www.emberjs.com/api/ember/release/classes/Component)
  @action
  didChangeTextField(this: MyComponent, event: KeyboardEvent) {
    const text = (event.target as HTMLInputElement).value;

    this.set('textProperty', text);
  }
}

Ember provides numerous template helpers for abstracting away menial event-handling configuration (such as avoiding configuring keyup, paste, keydown, etc.) via
Handlebars. Handlebars is a superset of HTML, rather than a separate markup/templating language like what React has.

The key differences from React is that the value attribute can be just set to the property for two-way data binding and the change event can be handeled via onChange in an {{input}} helper or oninput if using the <input /> element.

For more information and a list of helpers, see the Ember guides on input-helpers

Ember provides a few ways to invoke actions. The simplest way to invoke an action is via a closure with a named reference to the action:

<input
  value={{textProperty}}
  oninput={{action didChangeTextField}} />

Typically, in an Ember project, you would need an action handler for this kind of event only if you needed to do some pre-processing or logic on the value before eventually setting the value to destined property.

If no processing needs to occur on the value, using Ember provides two-way binding:

{{input value=textProperty}}

Ember’s templating provides good extensibility. As demonstrated by DockYard’s ember-composable-helpers addon, additional composability can be achieved for simplifying menial tasks.

For example:

<button onclick={{action (mut count) (inc count)}}>
  Increment count
</button>

Event Handling: Not all that different

Both React and Ember can achieve event handling in the same way. It may seem odd at first to have to follow convention in Ember’s case with @action decorators, but Ember’s goal is different from React’s: Ember provides a set of rules and conventions to follow to reduce decision fatigue when building applications whereas React nearly lets you do anything (for better and for worse).

As one can imagine, manually managing events for every single input in an app can feel tedious. However, Ember has a number of helpers to aid with the menial tasks. Fortunately, a number of tools and libraries (for both React and Ember) can assist with more complex things, such as forms — but that’ll be a topic for another time.


 

The application code for both the Ember and React code above can be found in this repository.
For both Ember and React apps, you should be able to get up and running, provided you have Docker installed, by running ./run docker (at least on UNIX-based machines).


In order to try to stay focused, I made a lot of assumptions about the readers’ knowledge.
If you want to know more about anything mentioned in this post, feel free to tweet at me [@NullVoxPopuli](https://twitter.com/nullvoxpopuli).

Published June 19th, 2018 by:
  • Preston Sego