ref – http://chineseruleof8.com/code/index.php/2020/02/24/this-context/
https://www.freecodecamp.org/news/this-is-why-we-need-to-bind-event-handlers-in-class-components-in-react-f7ea1a6f93eb/
First, some basics.
In unrestricted, es5 environment:
– when we call a standalone function, the ‘this’ gets dynamically bound to the global object.
– when we call a method of a method, the ‘this’ gets dynamically bound to the global object.
1 2 3 4 5 6 7 8 |
function Greetings() { console.log('standalone -----> ', this); // global function haha() { console.log('standalone haha ------> ', this); // global } haha(); } Greetings(); |
If you had an object (or instance) calling the method, you’ll get the ‘this’ gets bound to the object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var obj = { hoho: function() { // method //console.log(this); // obj function hehe() { // within method //console.log(this); // global function haha() { // within method console.log(this); // global } haha(); } hehe(); } } obj.hoho(); |
However, if you were to use strict mode, ‘this’ inside functions that are standalone, or within a method, would reference undefined
This happens in React because React uses Modules. Modules are strict by default. Thus, ‘this’ in React is null.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
'use strict' function Greetings() { function haha() { console.log('standalone haha ------> ', this); // due to strict: undefined } haha(); return { print : function() { // method console.log('print -->', this); // calling instance function hehe () { console.log('hehe', this); // undefined } hehe(); } } } let g = new Greetings(); // create instance g.print(); let outside = g.print; outside(); // print --> undefined // hehe undefined |
Notice how when we have a reference to the prototype function, the calling environment is global. The calling object is actually Global/Window.
Thus, in strict mode, it becomes undefined, rather than displaying the calling instance.
If we don’t use use strict, result would be:
1 2 3 4 |
let outside = g.print; outside(); // print --> Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …} // hehe Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …} |
We see that the calling object is Global (or Window in Elements)
This also holds true in function of function.
Simulating event handler executions
Let’s take a look at an example that simulates an event being passed and our handler being executed:
1 2 3 4 5 6 7 8 9 10 11 12 |
class Foo { // simulates a React component constructor(name){ this.name = name } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display(); // Saurabh |
We first execute prototype function display. Hence, the ‘this’ gets dynamically bound to the calling instance. It exists, and then looks for property ‘name’, which it finds. That is why we can successfully log Saurabh.
Now let’s look at the React situation. Initially you get something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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") ); |
Notice that when you click, the ‘this’ is undefined. WHY!?
The reason is that when handlClick is executed by the web environment, it is being executed in a different environment, and a reference somewhere points to our instance’s display function.
Let’s simulate it like this:
1 2 3 4 5 6 |
// The assignment operation below simulates loss of context // similar to passing the handler as a callback in the actual // React Component var outsideVar = foo.handleClick; outsideVar(); // TypeError: this is undefined |
Notice foo is not executing handleClick. Rather another reference is executing it. And that reference’s environment here is global.
But 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, ‘this’ in standalone functions should be pointing to the global object?
This is the not the case because because when we have another reference pointing to
– constructors,
– static functions,
-prototype methods,
they are executed in strict mode. Getter and setter functions are also executed in strict mode.
And when this happens in the global environment, they are returned undefined. This is why handlers in React components will dynamically point this to undefined. The function is running in strict mode, and the executing reference is global.
this is why in React class components, we do this:
1 2 3 4 |
constructor(props) { super(props); this.handleClick= this.handleClick.bind(this); } |
that way, when the web environment creates references to react component method and executes, its ‘this’ will point to the class itself.
2020 way of doing this
ref – https://medium.com/front-end-weekly/do-i-still-need-to-bind-react-functions-in-2019-6d0fe72f40d7
Use arrow functions. setText function gets returned whenever onClick is executed.
1 |
<button onClick={() => this.setText()}>Click me</button> |
Use class field syntax for method declaration:
1 2 3 4 5 6 7 |
class MyComponent extends React.Component { ... setText = () => { this.setState({ text: "123" }); } ... } |
Notice that onClick references a curried function. This means whenever the web environment receives a user click, it will execute
1 |
() => this.setText() |
whereas setText’s this is already bound to MyComponent via arrow syntax