React — Event handling, binding, ES6 classes and public class fields

There seems to be a lot of misconceptions about the different methods of setting up event handlers in React, especially about what goes on behind the scenes. Here I tried to group together some of the fundamental aspects that get frequently left out.

First of all look at these two blocks of code. The first is ES6 with an experimental public class field (the foo function). The other is plain ES5.

So this…

…is the same as this:

ES6 classes are just syntactic sugar over prototypal inheritance. An instance of Button will have two fields: handleClick and publicClassFieldFunction.

If we try accessing a non-existing property in that instance, it will go up the next object in prototype chain and look for something with that name. That object is Button.prototype, which has the methods handleClick and foo.

By looking at the code above you can see that, in the ES6 class syntax, declaring a property like this: handleClick() {...} means that the function will live in the.prototype of the class, which means it will be shared by every instance. Declaring it like this handleClick = function() {...} means that it will be set in the constructor and each instance will have one copy of it.

That means that there are two different handleClick functions: the one in the constructor and the one in the prototype.

Now let focus a bit on event handling in React. The documentation suggests this approach:

1 — Why is binding required?

Because this happens:

this is evaluated when the code runs. It depends on how the function gets called. If there are no dots (like in newVar()), this will be the window object. In strict mode, it would be undefined. Behind the scenes, React uses their event system and along the way has no way of knowing the instance of the object that should be the context. Instead of letting this be window or undefined, React will bind it to null. To make sure that this points correctly to the instances you can either use .bind or arrow functions.

2 — In that case, why not use an arrow function inside the render function? That way we could skip the .bind in the constructor.

Yes, arrow functions make sure that this is correctly bound to the instance and that would solve the problem. However, you’re passing a new inline arrow function every time render gets called. That means that there’s always a new reference, which might cause re-rendering.

3 — Why not binding inside the render function?

<button onClick={this.handleClick.bind(this)}>

The bind method returns a new function, so we have the same problem as above.

4 — What about public class fields?

Those work well. The problem is that they’re not officially part of the ECMAScript spec yet. They probably will though. Again, it’s important to point out that public class fields are just a different way of setting properties in the constructor. That is, each instance will get a copy. It’s still suggested in the documentation because performance implications are negligible.

5 — Well, if public class fields are not part of the spec and are just a different way of setting properties in the constructor , and arrow functions are officially part of the spec, why not using them directly in the constructor instead?

The documentation doesn’t suggest doing that, probably to avoid polluting the constructor with large function bodies.

6 — But by using the recommended .bind version in the constructor…

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

…we’re already polluting it. Also, by doing that, we’re writing more by both including a line in the constructor and declaring the function in the prototype with this bit:

True, but there’s a subtle difference due to how the .bind method works. First, just to recapitulate, if you declare the function in the constructor or as a public class field using arrow functions, every instance of the class will have a copy of the exact same function. And if you declare the function using the usual method syntax, it will be placed in the .prototype of the class to be shared by each instance. But what you’re doing with .bind, is creating a new function which wraps around the other one, giving it the instance’s context ( this ). That means that the body of the handler with the actual important logic will live in the prototype object shared by every instance, but each instance will have a new (tiny) function that provides the context.

7 — Waaaaait… but I thought that…

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

… was doing something like x = x + 1; You know, replacing a value with a new value that gets generated using the previous value. Those two this.handleClick refer to the same thing, right?

Nope. The first this.handleClick refers to a property that will be created in every instance, because it’s being set in the constructor. The second this.handleClick refers to an already existing property living in the prototype object of the class. Before that line executes, this.handleClick isn’t an actual property of the instance yet. If you console.log it, it will show up, but only because it will go up the prototype chain and get the one living in the prototype which gets set before instantiation (the actual handler). If you do this…

… you’ll see that the instance itself will only have its own handleClick property after being set with bound function that wraps the prototype’s handleClick.

The function body that will get called by every instance is the one living in the prototype after the this.handleClick on the left calls it with the proper context.

Those two this.handleClick functions don’t have to have the same name at all. Just try doing something like…

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

…and then make sure that what is passed to onClick in the render function is the this.handleClickFoo. It still works. A lot of people got confused simply because they have the same name in the documentation.

Try logging a bound function in the developer tools. You’ll see something like this:

In our case, the [[TargetFunction]] is the one in the prototype object. As for the bound function that wraps it, it will live in each instance providing the instance’s this to the [[TargetFunction]] .

That’s it. Thanks for reading, if you found this article helpful, feel free to clap so others will see it.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store