by Saurabh Misra

This is why we need to bind event handlers in Class Components in React

6zTp3-o8f4fKLBdGJJWln3nXtONaqALbEZdI
Background photo by Kaley Dykstra on Unsplash, source code image generated at carbon.now.sh

While working on React, you must have come across controlled components and event handlers. We need to bind these methods to the component instance using .bind() in our custom component’s constructor.

class Foo extends React.Component{
  constructor( props ){
    super( props );
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick(event){
    // your event handling logic
  }
  
  render(){
    return (
      <button type="button" 
      onClick={this.handleClick}>
      Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);

In this article, we are going to find out why we need to do this.

I would recommend reading about .bind() here if you do not already know what it does.

Blame JavaScript, Not React

Well, laying blame sounds a bit harsh. This is not something we need to do because of the way React works or because of JSX. This is because of the way the this binding works in JavaScript.

Let’s see what happens if we do not bind the event handler method with its component instance:

class Foo extends React.Component{
  constructor( props ){
    super( props );
  }
    
  handleClick(event){
    console.log(this); // 'this' is undefined
  }
    
  render(){
    return (
      <button type="button" onClick={this.handleClick}>
        Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);

If you run this code, click on the “Click Me” button and check your console. You will see undefined printed to the console as the value of this from inside the event handler method. The handleClick() method seems to have lost its context (component instance) or this value.

How ‘this’ binding works in JavaScript

As I mentioned, this happens because of the way this binding works in JavaScript. I won’t go into a lot of detail in this post, but here is a great resource to understand how the this binding works in JavaScript.

But relevant to our discussion here, the value of this inside a function depends upon how that function is invoked.

Default Binding

function display(){
 console.log(this); // 'this' will point to the global object
}

display(); 

This is a plain function call. The value of this inside the display() method in this case is the window — or the global — object in non-strict mode. In strict mode, the this value is undefined.

Implicit binding

var obj = {
 name: 'Saurabh',
 display: function(){
   console.log(this.name); // 'this' points to obj
  }
};

obj.display(); // Saurabh 

When we call a function in this manner — preceded by a context object — the this value inside display() is set to obj.

But when we assign this function reference to some other variable and invoke the function using this new function reference, we get a different value of this inside display() .

var name = "uh oh! global";
var outerDisplay = obj.display;
outerDisplay(); // uh oh! global

In the above example, when we call outerDisplay(), we don’t specify a context object. It is a plain function call without an owner object. In this case, the value of this inside display() falls back to default binding. It points to the global object or undefined if the function being invoked uses strict mode.

This is especially applicable while passing such functions as callbacks to another custom function, a third-party library function, or a built-in JavaScript function like setTimeout .

Consider the setTimeout dummy definition as shown below, and then invoke it.

// A dummy implementation of setTimeout
function setTimeout(callback, delay){

   //wait for 'delay' milliseconds
   callback();
   
}

setTimeout( obj.display, 1000 );

We can figure out that when we call setTimeout, JavaScript internally assigns obj.display to its argument callback .

callback = obj.display;

This assignment operation, as we have seen before, causes the display() function to lose its context. When this callback is eventually invoked inside setTimeout, the this value inside display() falls back to default binding.

var name = "uh oh! global";
setTimeout( obj.display, 1000 );

// uh oh! global

Explicit Hard Binding

To avoid this, we can explicitly hard bind the this value to a function by using the bind() method.

var name = "uh oh! global";
obj.display = obj.display.bind(obj); 
var outerDisplay = obj.display;
outerDisplay();

// Saurabh

Now, when we call outerDisplay(), the value of this points to obj inside display() .

Even if we pass obj.display as a callback, the this value inside display() will correctly point to obj .

Recreating the scenario using only JavaScript

In the beginning of this article, we saw this in our React component called Foo . If we did not bind the event handler with this , its value inside the event handler was set as undefined.

As I mentioned and explained, this is because of the way this binding works in JavaScript and not related to how React works. So let’s remove the React-specific code and construct a similar pure JavaScript example to simulate this behavior.

class Foo {
  constructor(name){
    this.name = name
  }
  
  display(){
    console.log(this.name);
  }
}

var foo = new Foo('Saurabh');
foo.display(); // Saurabh

// The assignment operation below simulates loss of context 
// similar to passing the handler as a callback in the actual 
// React Component
var display = foo.display; 
display(); // TypeError: this is undefined

We are not simulating actual events and handlers, but instead we are using synonymous code. As we observed in the React Component example, the this value was undefined as the context was lost after passing the handler as a callback — synonymous with an assignment operation. This is what we observe here in this non-React JavaScript snippet as well.

“Wait a minute! Shouldn’t the this value point to the global object, since we are running this in non-strict mode according to the rules of default binding?” you might ask.

No. This is why:

The bodies of class declarations and class expressions are executed in strict mode, that is the constructor, static and prototype methods. Getter and setter functions are executed in strict mode.

You can read the full article here.

So, to prevent the error, we need to bind the this value like this:

class Foo {
  constructor(name){
    this.name = name
    this.display = this.display.bind(this);
  }
  
  display(){
    console.log(this.name);
  }
}

var foo = new Foo('Saurabh');
foo.display(); // Saurabh

var display = foo.display;
display(); // Saurabh

We don’t need to do this in the constructor, and we can do this somewhere else as well. Consider this:

class Foo {
  constructor(name){
    this.name = name;
  }
  display(){
    console.log(this.name);
  }
}

var foo = new Foo('Saurabh');
foo.display = foo.display.bind(foo);
foo.display(); // Saurabh

var display = foo.display;
display(); // Saurabh

But the constructor is the most optimal and efficient place to code our event handler bind statements, considering that this is where all the initialization takes place.

Why don’t we need to bind ‘this’ for Arrow functions?

We have two more ways we can define event handlers inside a React component.

class Foo extends React.Component{
  handleClick = () => {
    console.log(this); 
  }
 
  render(){
    return (
      <button type="button" onClick={this.handleClick}>
        Click Me
      </button>
    );
  }
} 

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);
class Foo extends React.Component{
 handleClick(event){
    console.log(this);
  }
 
  render(){
    return (
      <button type="button" onClick={(e) => this.handleClick(e)}>
        Click Me
      </button>
    );
  }
}

ReactDOM.render(
  <Foo />,
  document.getElementById("app")
);

Both of these use the arrow functions introduced in ES6. When using these alternatives, our event handler is already automatically bound to the component instance, and we do not need to bind it in the constructor.

The reason is that in the case of arrow functions, this is bound lexically. This means that it uses the context of the enclosing function — or global — scope as its this value.

In the case of the public class fields syntax example, the arrow function is enclosed inside the Foo class — or constructor function — so the context is the component instance, which is what we want.

In the case of the arrow function as callback example, the arrow function is enclosed inside the render() method, which is invoked by React in the context of the component instance. This is why the arrow function will also capture this same context, and the this value inside it will properly point to the component instance.

For more details regarding lexical this binding, check out this excellent resource.

To make a long story short

In Class Components in React, when we pass the event handler function reference as a callback like this

<button type="button" onClick={this.handleClick}>Click Me</button>

the event handler method loses its implicitly bound context. When the event occurs and the handler is invoked, the this value falls back to default binding and is set to undefined , as class declarations and prototype methods run in strict mode.

When we bind the this of the event handler to the component instance in the constructor, we can pass it as a callback without worrying about it losing its context.

Arrow functions are exempt from this behavior because they use lexical this binding which automatically binds them to the scope they are defined in.