3

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

Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
Sendai
  • 1,625
  • 2
  • 8
  • 14
  • 1
    Are you sure the functional component prints `window` with that exact code? I'm getting `undefined`. ReactJS doesn't bind it's `onClick` to the element like traditional `onClick` HTML attributes do. – Nick Parsons Aug 23 '22 at 02:41
  • @NickParsons yup. just added a codesandbox link. i was wondering about traditional onclick methods as well so thanks for that info – Sendai Aug 23 '22 at 02:53
  • 1
    In non-strict mode, `this` in standalone non-arrow functions refers to window. But in strict-mode, `this` in such functions is undefined. Inside ES6 classes, strict mode is always enabled. – qrsngky Aug 23 '22 at 02:54
  • @qrsngky is onClick different in class components vs functional components? I wouldn't think the definition context would matter since it is being passed as a callback to onClick – Sendai Aug 23 '22 at 03:00
  • 1
    If I add `"use strict";` as the first statement inside the function in line 5 (before `console.log(this);`), it logs `undefined`. Even though ESLint says it's unnecessary, the transpilation results show that it does make a difference. Try this: `const handleClick = function() {"use strict"; console.log(this);};` both buttons will log 'undefined'. – qrsngky Aug 23 '22 at 03:01
  • The function body may look the same, but using a class makes it behaves as if there is an implicit "use strict", and that can make a difference. Simple examples: `class A{foo(){console.log(this)}}; test1 = (new A).foo; test1(); //undefined` vs `function test2(){console.log(this)}; test2(); //Window {...}` (in non-strict mode) vs `function test3(){"use strict";console.log(this)}; test3(); //undefined` – qrsngky Aug 23 '22 at 03:11
  • And if you changed line 4 to `const handleClick = () => {` then the `this` refers to the whatever `this` means in `App`. Then the "use strict" makes a difference if it is placed first in App, not the arrow function. – qrsngky Aug 23 '22 at 03:18
  • My apologies, I've reopened the question as the linked question did not apply to react. However, React documentation provides the information that event handlers are not bound to a class automatically, so will not have a defined `this` value by default when used as a handler., [ [Ref](https://reactjs.org/docs/handling-events.html), under "try it on codepen" ] – traktor Aug 23 '22 at 04:40
  • Does anyone know where to look for in the source code of React? It should use addEventListener somewhere. – qrsngky Aug 23 '22 at 06:11
  • I found this: https://github.com/facebook/react/blob/main/packages/react-dom/src/events/EventListener.js but not sure how they use it to deal with 'onClick' prop – qrsngky Aug 23 '22 at 06:30

1 Answers1

4

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:

  • non-strict mode: this = window (in browsers)
  • strict mode: 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):

enter image description here

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.

Nick Parsons
  • 45,728
  • 6
  • 46
  • 64