Reputation: 1741
Why is 'this' (the context) different depending on whether I use a functional or class component in react? I understand how binding works and that my handler will take on the context of the onclick handler (or however it gets used in onclick) but I don't understand why the value is different for these 2 situations. The functional component prints the window object while the class component prints undefined.
How is the synthetic onclick event defined? What is its context? What does it do with its callback?
export default function App() {
const handleClick = function () {
console.log(this);
};
return (
<div className="App">
<button onClick={handleClick}>HandleClick Functional</button>
<Toggle />
</div>
);
}
class Toggle extends React.Component {
handleClick() {
console.log(this);
}
render() {
return <button onClick={this.handleClick}>HandleClick Class</button>;
}
}
codesandbox link: https://codesandbox.io/s/onclick-context-test-ievog6?file=/src/App.js
Upvotes: 3
Views: 267
Reputation: 50684
TL;DR - This looks like a bug with CodeSandbox. Your React function component code ends up running in non-strict mode, which causes your this
value to default to the global object instead of undefined
as it should be.
This behaviour is pretty odd. But like @qrsngky has correctly pointed out in the comments above, the issue is mainly to do with strict-mode and a result of how CodeSandbox does error handling. Below I've outlined my observations on why your code is behaving as it is.
Functions declared with the function
keyword have their own value for this
(their own "this binding"). How that value for this
is set is based on how the function is called. Methods such as .call()
and .apply()
are able to set the this
value for the function they execute.
In strict-mode code, using fn.apply(undefined)
results in the function fn
being executed and the this
value within fn
being set to undefined
. However, in non-strict mode code, using fn.apply(undefined)
results in the this
value being set to the global object, which in browsers is the window
object. So you have different behaviors when a function is called with undefined
for its this
-binding, in:
this
= window
(in browsers)this
= undefined
When React invokes your onClick
handler function (func
), it invokes it by doing func.apply(context, ...)
, with a context
set to undefined
(you can see this via a debugger):
At this point, you may be thinking that your code doesn't use the "use strict"
directive anywhere, so it makes sense that when your function is called by React with a this
of undefined
it defaults to the global object window
as you're code isn't running in strict mode. However, it's not as straightforward as that ;). While it may not look like any of your code is running in strict mode, it is actually classified as strict mode code. That's because JavaScript Modules (what you've written your react code in), automatically run in strict mode. Similarly, code within classes also automatically runs in strict mode. So that begs the question as to why your code is showing window
then if your code should actually be running in strict mode.
When React code is written it needs to be transpiled into code that the browser can understand and execute. What seems to have occurred is that when your react code was transpiled, CodeSandbox has added a try-finally wrapper around your react code (you can see this using a debugger with source-maps disabled):
...
try {
"use strict";
...
function App() {
const handleClick = function() {
console.log(this);
}
...
}
} finally {
...
}
We can see that CodeSandbox has attempted to wrap your code within a try-finally block, where it appears as though the try
block runs in "strict mode" to try and match the same strict mode behavior automatically applied by the JavaScript module your code is originally written in. However, applying "use strict"
in a block {}
that isn't associated with a function has no effect on the code's strictness (see here for rules on strict mode). As a result, the try-finally wrapper that CodeSandbox is applying ends up running your code in non-strict/sloppy mode. This causes this
to be window
when React calls your onClick handler with undefined
.
On the other hand, your class
still works as expected because in the transpiled code it remains as a class
, so the strict mode behavior around classes remains, and so you see undefined
as expected.
When using React functional components, however, you shouldn't need to refer to this
, and instead can use hooks such as useState()
to manage things like state, or the event object (event.target
) to access the DOM element that was clicked.
Upvotes: 4