Search

Choosing the Best Approach for React Event Handlers

Josh Justice

7 min read

May 30, 2018

Choosing the Best Approach for React Event Handlers

One of React’s strengths is the simplicity of the code it allows you to create. There’s one part of React code that has always seemed overly complex to me, though: event handlers in class components.

Event handlers in React are passed as simple functions. When using class components, event handlers are usually defined as methods on the class. But passing an object’s method as an event handler runs into JavaScript’s infamous this problem. There are a few different ways around it, but none of them has seemed ideal to me.

A proposed ECMAScript syntax for class properties provides a way to get code that is both clean and reliable. Does that mean we should use class properties for our event handlers? Let’s find out.

The Problem

When you want to tell a React component how to respond to an event, you pass a function to it as an event handler. With ES6 classes, your first instinct might be to do it like so:

class MyComponent extends React.Component {
  showValue() {
    console.log(this.props.value);
  }

  render() {
    return (
      <button onClick={this.showValue}>Do Something</button>
    );
  }
}

If you try running this code and clicking the button, you get a TypeError: Cannot read property 'props' of null. In other words, in showValue() the value of this is null, so when you try to access this.props you get an error.

This isn’t a React-specific problem; it happens in JavaScript any time an object’s method is assigned to a variable:

class MyClass {
  constructor(value) {
    this.value = value;
  }

  getValue() {
    return this.value;
  }
}

const myInstance = new MyClass(27);
myInstance.getValue(); // -> 27

const getValueFunction = myInstance.getValue;
getValueFunction(); // -> TypeError: Cannot read property 'value' of undefined

Why do methods lose access to this when assigned to a variable? It’s because in JavaScript the value of this is set at the time you invoke a function. The problem isn’t assigning the function to a variable per se; the problem is that when you call that function you aren’t calling it on the original object. It’s as though the function had been defined like this:

const getValueFunction = function() {
  return this.value;
};
getValueFunction(); // -> TypeError: Cannot read property 'value' of undefined

So how can we pass object methods as event handlers to preserve their access to this?

Bound Functions

One solution is to use Function.prototype.bind() to set the value of this at the time of construction:

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

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

  showValue() {
    console.log(this.props.value);
  }

  render() {
    return (
      <button onClick={this.showValue}>Do Something</button>
    );
  }
}

We define showValue() as a normal ES6 method. Then in the constructor we overwrite this.showValue with the version returned by this.showValue.bind(this). What does bind() do? It returns a version of the function that has the value of this bound to the passed-in object.

This solves the problem, but it also makes methods on the object behave in a way that’s different from what’s expected in JavaScript. If you forget to bind a method, then pass it as an event handler, the errors you get won’t make it immediately obvious what’s going on.

Inline Arrow Functions

Another solution is to pass an arrow function inline in an event attribute like onClick:

class MyComponent extends React.Component {
  showValue() {
    console.log(this.props.value);
  }

  render() {
    return (
      <button onClick={() => this.showValue()}>Do Something</button>
    );
  }
}

Note that we are no longer passing the this.showValue function as the onClick attribute directly. Instead, we call it from within an arrow function, () => this.showValue(). What will the value of this be within the arrow function? Arrow functions preserve the value of this from the context where they are defined. This arrow function is defined within the render() method of MyComponent, where the value of this is the MyComponent object. So when the arrow function is called, this will still be the MyComponent object, and the call to this.showValue() will succeed.

Arrow functions may be familiar because they’re the most natural solution when we want to pass a parameter to an event handler:

class MyComponent extends React.Component {
  showValue(value) {
    console.log(value);
  }

  render() {
    return (
      <button onClick={() => this.showValue(27)}>Show Value 27</button>
    );
  }
}

Class Property Arrow Functions

The new ES proposal for class properties provides an interesting alternative for using methods as event handlers. If you created your React app with create-react-app, then you have support for class properties already (at least as of react-scripts@1.0.12). For other JavaScript projects, you can use class properties with the Babel transform-class-properties plugin.

Here’s how class properties work for simple values:

class MyClass {
  myProperty = 27;
}

const myInstance = new MyClass();
myInstance.myProperty; // -> 27

Class properties can be used for more than just simple values, though: you can also assign functions to them.

class MyClass {
  myFunction = () => 27;
}

const myInstance = new MyClass();
myInstance.myFunction(); // -> 27

How do class property arrow functions help us use methods as event handlers? As we saw earlier, arrow functions use the value of this from the context in which they were defined. For arrow functions defined in an ES6 class, this will be the object they’re defined on. This means a class property arrow function can be passed as a React event handler as-is:

class MyComponent extends React.Component {
  showValue = () => {
    console.log(this.props.value);
  };

  render() {
    return (
      <button onClick={this.showValue}>Do Something</button>
    );
  }
}

Incidentally, if you assign a function defined with the function keyword to a class property, it won’t preserve the value of this:

class MyComponent extends React.Component {
  showValue = function() {
    console.log(this.props.value) // -> TypeError: Cannot read property 'props' of undefined
  };

  render() {
    return (
      <button onClick={this.showValue}>Do Something</button>
    );
  }
}

The reason it doesn’t work is because functions declared with the function keyword don’t preserve the value of this from their original context. They define this when the function is invoked.

Performance

So class properties arrow function are all upsides, right? Not quite. Nicolas Charpentier wrote a blog post describing some downsides of class property arrow functions. He pointed out problems they cause with mocking methods and inheritance, but those don’t worry me because I tend to avoid those techniques. But his point about performance is well taken. Currently class property arrow functions transpile to arrow functions assigned within the class’s constructor:

// source:
class MyComponent extends React.Component {
  showValue = () => {
    console.log(this.props.value);
  };
  ...
}

// transpiles to:
class MyComponent extends React.Component {
  constructor() {
    this.showValue = () => {
      console.log(this.props.value);
    };
  }
  ...
}

This means that we aren’t taking advantage of JavaScript’s prototype mechanism for sharing functions amongst class instances. Instead, a new function is created in memory for each instance you create.

Now, JavaScript also has to do extra work when you call .bind() or use an inline arrow function, so in that sense class property arrow functions aren’t inherently slower. But if we apply this approach beyond our event handlers and define all our methods as class property arrow functions, that means instantiating extra functions that wouldn’t be instantiated in the other approaches. It may not cause any measurable performance problems in our apps today. But once we create a component with internal methods that is used hundreds of times in our app, it could bite us.

What to choose?

So which approach should we use for our methods? The ideal approach to writing methods should be:

  • Clear: it should be easy to tell what is going on in the code.
  • Performant: it shouldn’t unnecessarily slow down the app.
  • Universal: it should be appropriate to use for most or all methods, both for components and non-component classes.

The option that best meets these criteria is to use inline arrow functions. The other approaches have significant downsides:

  • Binding in the constructor isn’t clear, because you don’t see the binding either at the place the method is defined or where it’s used. This makes it easy to make mistakes and miss a binding.
  • Class property arrow functions require you to sacrifice either performance or universality. If you use them everywhere they are likely to slow down your app. And if you use them only for event handlers then your method definitions are inconsistent.

By contrast, inline arrow functions meet all of the criteria:

  • Clear: they make code easy to read because the binding happens at the point of use, so you can feel confident that this will be correct. This approach uses ES6 methods and arrow functions in a straightforward way, and sticks with the normal JavaScript behavior of methods with regard to this
  • Performant: they can be used for all our methods without hurting performance, because they only result in the extra work of binding when it’s absolutely necessary.
  • Universal: there’s nothing that would require us to use a different approach for some class methods.

If we come to the point where browsers support class property arrow functions natively and performantly, then my recommendation would change to use class property arrow functions. But today, inline error functions are the best fit.

What do you think–do the advantages of inline arrow functions convince you to use them for React event handlers? Or is there a different tradeoff for your project? Let us know!

Josh Justice

Author Big Nerd Ranch

Josh Justice has worked as a developer since 2004 across backend, frontend, and native mobile platforms. Josh values creating maintainable systems via testing, refactoring, and evolutionary design, and mentoring others to do the same. He currently serves as the Web Platform Lead at Big Nerd Ranch.

Speak with a Nerd

Schedule a call today! Our team of Nerds are ready to help

Let's Talk

Related Posts

We are ready to discuss your needs.

Not applicable? Click here to schedule a call.

Stay in Touch WITH Big Nerd Ranch News