No bind or arrow functions in in JSX Props - Why? How?

Recently, I found myself running tslint on a small React application written in TypeScript. The combination itself is worth writing another blog post about, but today we’re covering just one of the rules that tslint has; in fact, it is defined in the “tslint-react” rule set. It is also included in the eslint-plugin-react, because it is not related to TypeScript per se.

What’s it about?

In its ES6 version, the rule says: “No .bind() or Arrow Functions in JSX Props (react/jsx-no-bind).” The TypeScript version has two rules, one is called “jsx-no-bind” and the other one “jsx-no-lambda”; their intent is the same as the ES6 one. When you first notice the rule, you might wonder “why is that an issue?”. If you call bind(), or if you define an arrow function, this will create a brand new function every time you run that code. When you’re using that inside a render() method or a so-called stateless functional component, it will create a new function every time you render that component. That violates the one strict rule from React that says

All React components must act like pure functions with respect to their props.

A pure function, according to Wikipedia, must

always evaluates the same result value given the same argument value(s)

If rendering a component defines a new function, this means that given the same set of props, defining a new function inside the rendering yields a different result every time you invoke that rendering and thus violates the aforementioned rule.

Okay, so we violated a React rule; now what?

This can actually be pretty serious. Imagine the following situation (in ES 6):

class Button extends React.PureComponent {
  render() {
    const { onClick } = this.props;
    console.log('render button');
    return (
      <button onClick={ onClick }>Click</button>
    );
  }
}

class Parent extends React.Component {
  state = {
    counter: 0
  }

  onClick() {
    this.setState((prevState) => ({
      counter: prevState.counter + 1
    }));
  }

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

    return (
      <div>
        <Button onClick={ () => this.onClick() } />
        <div>{ counter }</div>
      </div>
    );
  }
}

ReactDOM.render(
  <Parent />,
  document.getElementById('root')
);

Aside: note that the onClick() method of the Parent uses a trick that allows you to pass setState() a function, not an object; the function is called an updater and its return value will be the new state. Since state updates may be asynchronous, this approach guarantees that you’re always looking at the most recent state.

How to fix it?

Back to the arrow functions: every time the render() function of Parent is invoked, the <Button> component will receive a different function for its onClick prop. And that means it will be re-rendered every time again. This also happens when the onClick prop would have been this.onClick.bind(this), since that also creates a new function.

The fix is pretty easy: instead of defining an arrow function or invoking bind(), we need to use the same function every time again. We do that by referencing the function instead of invoking it: onClick={ this.onClick }. Note that there are no parentheses or brackets after the name of the function.

But now you may ask: what if I need one or more parameters to be passed? For example, I’m rendering a list of things and for each item in it I want a button that does something with that item:

class List extends React.Component {
  render() {
    const { onItemClick } = this.props;
    return (
      <ul>
        { this.props.items.map(item =>
          <li key={ item.id } onClick={ item => onItemClick(item) }>
            { item.name }
          </li>
        )}
      </ul>
    );
  }
}

const items = [
  { id: 1, name: 'One' },
  { id: 2, name: 'Two' },
]
ReactDOM.render(
  <List items={ items } />,
  document.getElementById('root')
);

The key here is to apply the “separation of concerns” design principle. Right now, our <List> actually has two concerns:

  1. it iterates over the list of items, and
  2. it renders each individual item.

Let’s pull the two apart: one component should iterate over the list, another component should render one item.

class Item extends React.Component {
  onClick() {
    this.props.onClick(this.props.item)
  }

  render() {
    return (
      <li onClick={ this.onClick }>{ item.name }</li>
    );
  }
);

class List extends React.Component {
  render() {
    const { onItemClick } = this.props;
    return (
      <ul>
        { this.props.items.map(item =>
          <Item key={ item.id } onClick={ this.onItemClick } item={ item } />
        )}
      </ul>
    );
  }
}

Digging a little deeper (TypeScript)

If you would be using TypeScript with React, there’s a little gotcha. If you had just copied the above ES6-code, tsc will be OK with that, but it wouldn’t work. Instead, you would need to define the onClick method using a TypeScript feature called “instance arrow function”.

class Item extends React.Component {
  onClick = () => {
    this.props.onClick(this.props.item)
  }

  render() {
    return (
      <li onClick={ this.onClick }>{ item.name }</li>
    );
  }
);

It might look strange at first sight, but the difference is quite clear if you use the TypeScript Playground to experiment a bit. Imagine we have a little TypeScript class like this:

class Demo {
    private myName = 'Example';

    public example1() {
        console.log(`Example 1: ${this.myName}`);
    }

    public example2 = () => {
        console.log(`Example 2: ${this.myName}`);
    }
}

When transpiling to JavaScript, we see:

var Demo = (function () {
    function Demo() {
        var _this = this;
        this.myName = 'Example';
        this.example2 = function () {
            console.log("Example 2: " + _this.myName);
        };
    }
    Demo.prototype.example1 = function () {
        console.log("Example 1: " + this.myName);
    };
    return Demo;
}());

And now it all makes sense: the first example1 refers to this, but as we know the this pointer in JavaScript can point to another execution context. The second example2 on the other hand is “fixed” in this regard; no matter from what context it was called, it will use _this and thus always use the right execution context.