Although the previous answers have provided the basic overview of solutions (i.e. binding, arrow functions, decorators that do this for you), I've yet to come across an answer which actually explains why this is necessary—which in my opinion is the root of confusion, and leads to unnecessary steps such as needless rebinding and blindly following what others do.
this
is dynamic
To understand this specific situation, a brief introduction to how this
works. The key thing here is that this
is a runtime binding and depends on the current execution context. Hence why it's commonly referred to as "context"—giving information on the current execution context, and why you need to bind is because you loose "context". But let me illustrate the issue with a snippet:
const foobar = {
bar: function () {
return this.foo;
},
foo: 3,
};
console.log(foobar.bar()); // 3, all is good!
In this example, we get 3
, as expected. But take this example:
const barFunc = foobar.bar;
console.log(barFunc()); // Uh oh, undefined!
It may be unexpected to find that it logs undefined—where did the 3
go? The answer lies in "context", or how you execute a function. Compare how we call the functions:
// Example 1
foobar.bar();
// Example 2
const barFunc = foobar.bar;
barFunc();
Notice the difference. In the first example, we are specifying exactly where the bar
method1 is located—on the foobar
object:
foobar.bar();
^^^^^^
But in the second, we store the method into a new variable, and use that variable to call the method, without explicitly stating where the method actually exists, thus losing context:
barFunc(); // Which object is this function coming from?
And therein lies the problem, when you store a method in a variable, the original information about where that method is located (the context in which the method is being executed), is lost. Without this information, at runtime, there is no way for the JavaScript interpreter to bind the correct this
—without specific context, this
does not work as expected2.
Relating to React
Here's an example of a React component (shortened for brevity) suffering from the this
problem:
handleClick() {
this.setState(({ clicks }) => ({ // setState is async, use callback to access previous state
clicks: clicks + 1, // increase by 1
}));
}
render() {
return (
<button onClick={this.handleClick}>{this.state.clicks}</button>
);
}
But why, and how does the previous section relate to this? This is because they suffer from an abstraction of the same problem. If you take a look how React handles event handlers:
// Edited to fit answer, React performs other checks internally
// props is the current React component's props, registrationName is the name of the event handle prop, i.e "onClick"
let listener = props[registrationName];
// Later, listener is called
So, when you do onClick={this.handleClick}
, the method this.handleClick
is eventually assigned to the variable listener
3. But now you see the problem arise—since we've assigned this.handleClick
to listener
, we no longer specify exactly where handleClick
is coming from! From React's point of view, listener
is just some function, not attached to any object (or in this case, React component instance). We have lost context and thus the interpreter cannot infer a this
value to use inside handleClick
.
Why binding works
You might be wondering, if the interpreter decides the this
value at runtime, why can I bind the handler so that it does work? This is because you can use Function#bind
to guarantee the this
value at runtime. This is done by setting an internal this
binding property on a function, allowing it to not infer this
:
this.handleClick = this.handleClick.bind(this);
When this line is executed, presumably in the constructor, the current this
is captured (the React component instance) and set as an internal this
binding of a entirely new function, returned from Function#bind
. This makes sure that when this
is being calculated at runtime, the interpreter will not try to infer anything, but use the provided this
value you given it.
Why arrow function properties work
Arrow function class properties currently work through Babel based on the transpilation:
handleClick = () => { /* Can use this just fine here */ }
Becomes:
constructor() {
super();
this.handleClick = () => {}
}
And this works due to the fact arrow functions do not bind their own this, but take the this
of their enclosing scope. In this case, the constructor
's this
, which points to the React component instance—thus giving you the correct this
.4
1 I use "method" to refer to a function that is supposed to be bound to an object, and "function" for those not.
2 In the second snippet, undefined is logged instead of 3 because this
defaults to the global execution context (window
when not in strict mode, or else undefined
) when it cannot be determined via specific context. And in the example window.foo
does not exist thus yielding undefined.
3 If you go down the rabbit hole of how events in the event queue are executed, invokeGuardedCallback is called on the listener.
4 It's actually a lot more complicated. React internally tries to use Function#apply
on listeners for its own use, but this does not work arrow functions as they simply do not bind this
. That means, when this
inside the arrow function is actually evaluated, the this
is resolved up each lexical environment of each execution context of the current code of the module. The execution context which finally resolves to have a this
binding is the constructor, which has a this
pointing to the current React component instance, allowing it to work.