Avoiding inline lambdas

Problem

class MyComponent extends React.Component {
  render() {
    // bad
    return <Button onClick={() => console.log('I was clicked')} />;
  }
}

The problem with the above code is that every time the render method of our MyComponent component is called, a new function will be created for our onClick handler. While this might sound like a premature optimization, there are legitimate performance concerns at Shopify's scale.

Worse yet, there is also another side-effect of creating a new function on every single render: If our Button component were implemented using PureComponent, it would effectively need to update on every single render. Since the onClick handler wouldn't be referentially equal to the previous one, it would think its props have changed.

Solution

Binding the handler in our component's constructor and passing the handler by reference:

class MyComponent extends React.Component {
  constructor(props) {
   super(props);

   this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log('I was clicked');
  }

  render() {
    return <button onClick={this.handleClick} />;
  }
}

There is also an @autobind decorator you can import from shopify/javascript-utilities to achieve the same effect. Decorators aren't part of the official EcmaScript spec yet, but you can use a babel transform to add support for them, or, if you're using TypeScript, they're already supported out of the box.

import {autobind} from 'shopify/javascript-utilities';

class MyComponent extends React.Component {
  @autobind
  handleClick() {
    console.log('I was clicked');
  }

  render() {
    return <button onClick={this.handleClick} />;
  }
}

One problem you might run into with this approach is when mapping over an array to render a list of elements.

class ItemList extends React.Component {
  @autobind
  renderItem(item, index) {
    return (
      <li>
        Item ##{index}
        <button onClick={this.handleClick}>Remove</button>
      </li>
    );
  }
  @autobind
  handleClick() {
    // How can you tell which item was clicked from here without creating an inline lambda above?
  }
  render() {
    return <ul>{this.props.items.map(this.renderItem)}</ul>;
  }
}

In this case, the solution would be to refactor our component to split out the item rendering logic into a separate component:

class ItemList extends React.Component {
  @autobind
  renderItem(item, index) {
    return (
      <Item index={index} onRemove={this.handleRemove} />
    );
  }
  @autobind
  handleRemove(index) {
    // Handle item removal here using the index
  }
  render() {
    return <ul>{this.props.items.map(this.renderItem)}</ul>;
  }
}

class Item extends React.Component {
  @autobind
  handleClick() {
    const {index, onRemove} = this.props;

    if (typeof onRemove === 'function') {
      onRemove(index);
    }
  }
  render() {
    const {index} = this.props;

    return (
      <li>
        Item ##{index}
        <button onClick={this.handleClick}>Remove</button>
      </li>
    );
  }
}

Last updated