Why bind event handlers in React?
In JavaScript, methods are not bound to their classes. This means that
methods of a class do not point to its instance. If a method needs
this
to refer to a class instance, we need to bind it. To
demonstrate this let's change the context of a method.
Here is a simple class:
class Test {
someMethod() {
this.handleEvent();
}
handleEvent() {
console.log('Hello');
}
}
We can change a method's context by using call()
which will give
it a different this
value.
const test = new Test();
const testTwo = {};
test.someMethod.call(testTwo);
Or we can use destructing assignment syntax available since ES6 release to pluck
out
someMethod
and assign it to a const
. By doing so we
will create a new execution context.
const test = new Test();
const { someMethod } = test;
test.someMethod();
When running this code you should see:
Bind using bind()
The way to solve this is to bind someMethod()
to
the class. This has to be done in the constructor upon object initialisation.
class Test {
constructor() {
this.someMethod = this.someMethod.bind(this);
}
someMethod() {
this.handleEvent();
}
handleEvent() {
console.log('Hello');
}
}
Now it does not really matter if create a new execution context or pass a different this value, the method will be bound to the class.
This is exactly why we have to bind event handlers in React class components.
import React, { Component } from 'react';
export default class SomeComponent extends Component {
handleBtnEvent(e) {
this.displayAlert(e.target.tagName);
}
displayAlert(tagName) {
alert(`I have been clicked and I am a ${tagName}!`);
}
render() {
return (
<button onClick={this.handleBtnEvent}>Click me!</button>
)
}
}
This component will error and you will see the following type error message:
Again we've got the same problem, this
inside handleBtnEvent()
method does not point to the instance of SomeComponent
class. To solve this we are going to bind it to the class.
import React, { Component } from 'react';
export default class SomeComponent extends Component {
constructor(props) {
super(props);
// bind handleBtnEvent to SomeComponent
this.handleBtnEvent = this.handleBtnEvent.bind(this);
}
handleBtnEvent(e) {
this.displayAlert(e.target.tagName);
}
displayAlert(tagName) {
alert(`I have been clicked and I am a ${tagName}!`);
}
render() {
return (
<button onClick={this.handleBtnEvent}>Click me!</button>
)
}
}
The method is now bound to the class and can access displayAlert()
method.
Bind using arrow function
We can also use arrow function in the callback to achieve the same result.
import React, { Component } from 'react';
export default class SomeComponent extends Component {
handleBtnEvent(e) {
this.displayAlert(e.target.tagName);
}
displayAlert(tagName) {
alert(`I have been clicked and I am a ${tagName}!`);
}
render() {
return (
<button onClick={(e) => this.handleBtnEvent(e)}>Click me!</button>
)
}
}
When using the arrow function, this
gets automatically lexically bound to the context of its enclosing scope. It could either be a function or global scope. So in our case it is the scope of SomeComponent
.
Final words
To summarise, whenever we pass event handlers as callbacks, the event handler method loses its context. The default binding gets set to undefined
and the error gets thrown. To avoid this we need to bind the event handler to the component instance to explicitly set the context of this
.