JSX and ES6

TypeScript is a JavaScript superset with a compiler that enforces the types. It’s also, though, one of those sexy new JavaScript flavors that implement “ES6”…actually, a family of modern JavaScript standards for more productive programming. When combined with other tooling, it also supports JSX, React’s sorta-templating system, but with TypeScript semantics.

We glazed over the ES6 and JSX (that is, TSX) in previous steps. Let’s take more of a look.

Cleanup

The previous steps added some stuff to show their topics. Let’s clean up a little bit.

Let’s change App.test.tsx back to two simple tests:

import { shallow } from 'enzyme';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {
    const div = document.createElement('div');
    ReactDOM.render(<App/>, div);
    ReactDOM.unmountComponentAtNode(div);
});

it('renders the heading', () => {
    const wrapper = shallow(<App/>);
    expect(wrapper.find('h1').text()).toBe('Hello React');
});

Also, our component in App.tsx:

import * as React from 'react';

class App extends React.Component {
    public render() {
        return (
            <div>
                <h1>Hello React</h1>
            </div>
        );
    }
}

export default App;

Make sure the test runner is still running and watching. You can, though, shutdown the start task. We don’t need it regenerating the bundle and updating the browser, duplicating what’s happening in the Jest process.

Our two tests pass. Let’s see some ES6 and TSX.

Classes and Fields

As you can see, this component is using React’s support for ES6 classes. Our App component extends React.Component which has a constructor that initializes a bunch of stuff. We’ll see functional components later.

Let’s do another approach at extracting the hardwired string from the <h1>. The render method can, of course, have scope. Let’s define the label there:

public render() {
    const label = 'Hello React';
    return (
        <div>
            <h1>{label}</h1>
        </div>
    );
}

The const is used because we never intend to re-assign the label. Our tests pass, so this change worked fine.

We can also move the label up to the class label as a field:

class App extends React.Component {
    public label = 'Hello React'

    public render() {
        return (
            <div>
                <h1>{this.label}</h1>
            </div>
        );
    }
}

We had to change the <h1> to use this.label, to get the value off the instance.

Arrow Functions

ES6 introduced small, inline anonymous functions called “arrow functions”. The are incredibly useful and have come to dominate frontend frameworks. Let’s see them in action for click handlers. We’ll start by showing something that doesn’t work until an arrow function saves the day.

We’ll first do an inline click handler that displays a static string. Note that JSX (and thus, TSX) map certain HTML attributes into first-class names, such as onClick and className, in its grammar, thus letting us assign an expression (with {}) instead of a string:

<h1 onClick={alert('Hello World')}>{this.label}</h1>

But this fails. Why? The expression is immediately evaluated, rather than run when the event is fired. We need a way to assign something that will be executed later, when the event is fired.

Arrow functions to the rescue! Try this instead:

<h1 onClick={() => alert('Hello World')}>{this.label}</h1>

What does this change do? It stores a function which is created on the fly and stored “anonymously” in that scope. The () means this arrow function needs no arguments. (It’s actually passed an event, which we’ll use in later steps.) The function body is one line, so we don’t need curly braces for a block.

This is actually valid TypeScript, and would compile and run, but our picky style linter complains. In React, defining functions on the fly is expensive (when you’re doing hundreds in a loop) and the TSLint community decided to frown on that by default. We can override the default, though. Edit tslint.json to contain:

{
  "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
  "linterOptions": {
    "exclude": [
      "config/**/*.js",
      "node_modules/**/*.ts"
    ]
  },
  "rules": {
    "jsx-no-lambda": false
  }
}

Restart your start script to get the linter to pick up the change. Everything compiles fine and clicking on the <h1> in the browser produces an alert.

Move To a Method

Inline handlers aren’t so smart, as they aren’t easily testable. Plus, bossy TSLint doesn’t like them (for performance reasons.) Let’s move the handler to a component method:

class App extends React.Component {
    public label = 'Hello React'

    public handleClick () {
        alert('Hello World');
    }

    public render() {
        return (
            <div>
                <h1 onClick={this.handleClick}>{this.label}</h1>
            </div>
        );
    }
}

We referenced this.handleClick but we didn’t call it. React will call it later, when we actually click.

Clicking on the heading works well, so let’s remove the "jsx-no-lambda": false rule from tslint.conf and restart the start script.

Let’s have the alert display the label by changing it to alert(this.label);. Uh-oh. Clicking on the heading produces a mile-long traceback in the JavaScript console. The traceback mentions HTMLUnknownElement. And that’s the problem: the this in the handleClick method isn’t the component instance, it is event. This is a chronic problem in React programming, causing the .bind syntax.

Arrow functions, though, get the correct this. We could change the handler to the following:

<h1 onClick={() => this.handleClick}>{this.label}</h1>

…but we’re back to the bossy TSLint complaint. Instead, we can bind the arrow function to the component:

public handleClick = () => {
    alert(this.label);
}

Look at that freaky approach! Instead of a method, we are binding a dynamic function to a class property. (Discussion below about the downsides.)

JSX

React brought innovation to the concept of templating languages by extending JavaScript itself. Your templating is mixed directly into your JavaScript file and component. TSX is the TypeScript flavor of JSX, with file extensions ending in .tsx.

The easiest way to see TSX in action? Go to your <h1> and try to add class="". TypeScript itself has JSX/TSX support in the compiler and gives a compiler error:

Property 'class' does not exist on type
'DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>'.

Also, the IDE refuses to autocomplete on class. It does, though, autocomplete on className, the JSX/TSX equivalent.

Accepting the autocomplete shows that the IDE fills in {} for an attribute value instead of double-quotes. What’s the difference? A double-quote contains a regular string, whereas brackets contain JavaScript expressions, which we saw above.

Note About Arrow Functions

Arrow functions look great on classes but behind the scenes they don’t really do what you think. Purists have pointed out the flaws (mockability, subclassing, performance.) And yet, they remain a very popular solution to binding in React and similar systems.