Now Available React Programming: The Big Nerd Ranch Guide
Front-End ReactBased on our React Essentials course, this book uses hands-on examples to guide you step by step through building a starter app and a complete,...
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.
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
?
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.
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>
);
}
}
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.
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.
So which approach should we use for our methods? The ideal approach to writing methods should be:
The option that best meets these criteria is to use inline arrow functions. The other approaches have significant downsides:
By contrast, inline arrow functions meet all of the criteria:
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
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!
Based on our React Essentials course, this book uses hands-on examples to guide you step by step through building a starter app and a complete,...
Svelte is a great front-end Javascript framework that offers a unique approach to the complexity of front-end systems. It claims to differentiate itself from...
Large organizations with multiple software development departments may find themselves supporting multiple web frameworks across the organization. This can make it challenging to keep...