Sendai
Sendai

Reputation: 1741

'this' context different for event handlers in function vs class react components

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

Answers (1)

Nick Parsons
Nick Parsons

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:

  • 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.

Upvotes: 4

Related Questions