3

Here's a bit of code, within a react component class (scaffolded using CRA 2)

  click = () => {
    console.log(this, "hello");
    let x = 1 + 1; //This is just here to let chrome put a break point here. 
  }

When this code runs, it will print the component object to the console.

However - if I attach a debugger to that point, both Chrome (68), and Firefox (63) will show 'this' as undefined.

What's going on here?

Is it something to do with the transform-class-properties babel plugin being used to create click as a class property?

Edit: Yes, that seems like exactly what it is.

If we manually bind the method like:

  constructor() {
    super();
    this.click2 = this.click2.bind(this);
  }

  click2() {
    console.log(this, "hello");
    let x = 1 + 1;
  }

then it works fine.

In any case - is there a convenient way to solve this, so I don't have to put all those bind statements in?

dwjohnston
  • 11,163
  • 32
  • 99
  • 194
  • 1
    I'd start by looking at what the transpiled code looks like – CertainPerformance Nov 28 '18 at 06:32
  • Are you sure each inherited instance doesn't get two sets of `click2` events attached to it? – Ahmad Nov 28 '18 at 06:33
  • Can you post what the transpiled code looks like? – CertainPerformance Dec 02 '18 at 05:01
  • @CertainPerformance - How would I do that? Given that we're talking about the dev server here. – dwjohnston Dec 03 '18 at 23:54
  • I think `transform-class-properties` puts all your class property functions into the constructor, and since the `debugger` is in a sub scope this probably happens: https://stackoverflow.com/questions/28388530/why-does-chrome-debugger-think-closed-local-variable-is-undefined – aravindanve Dec 05 '18 at 21:26
  • It is hard to give you a full answer because your post is missing important context. Please post a [complete example](https://stackoverflow.com/help/mcve). – Old Pro Dec 10 '18 at 11:39

3 Answers3

5

I created an example on CodeSandbox that I think reproduces your issue, though I'm not sure. Please create your own example if it does not. The relevant code is included below.

In this example, the code works fine. console.log(this, "hello") logs a Square object + "hello" as you might expect. If you put a breakpoint on the let y = 2 + 2 line, the Chrome debugger will show

this: undefined
x: 2
y: undefined

Of course, y is undefined because the let y statement has not executed yet. x is defined, as expected. this is undefined because React and Babel are jumping through lots of hoops under the covers, and this is, in fact, undefined. If you want to access this from the debugger, you need to use _this. In fact, even though you put a breakpoint on the line let y = 2 + 2, that is not the actual source being executed or where the actual breakpoint is. What you are seeing is a convenience provided by a source map that lets you view and set a breakpoint on the code you wrote despite the fact that the actual code being run is completely different (the result of processing by Babel etc.).

The code I wrote is:

class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null
    };
  }

  click = () => {
    console.log(this, "hello");
    let x = 1 + 1; //This is just here to let chrome put a break point here.
    let y = 2 + 2; //This is just here to let chrome put a break point here.
  };

  render() {
    return (
      <button className="square" onClick={this.click}>
        {this.props.value}
      </button>
    );
  }
}

the code actually running is:

var Square =
/*#__PURE__*/
function (_React$Component) {
  (0, _inherits2.default)(Square, _React$Component);

  function Square(props) {
    var _this;

    (0, _classCallCheck2.default)(this, Square);
    _this = (0, _possibleConstructorReturn2.default)(this, (0, _getPrototypeOf2.default)(Square).call(this, props));
    (0, _defineProperty2.default)((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "click", function () {
      console.log((0, _assertThisInitialized2.default)((0, _assertThisInitialized2.default)(_this)), "hello");
      var x = 1 + 1; //This is just here to let chrome put a break point here.

      var y = 2 + 2; //This is just here to let chrome put a break point here.
    });
    _this.state = {
      value: null
    };
    return _this;
  }

  (0, _createClass2.default)(Square, [{
    key: "render",
    value: function render() {
      return _react.default.createElement("button", {
        className: "square",
        onClick: this.click
      }, this.props.value);
    }
  }]);
  return Square;
}(_react.default.Component);

Because of the React.js internals (in particular, the way it wraps events), by the time the handler is called, this is undefined. If you look at the call stack, you see that executeDispatch calls invokeGuardedCallbackAndCatchFirstError with an explicit value of undefined for the context object that is ultimately the value of this inside the callback. React and Babel try to hide all this from you when you are writing code, but they cannot completely hide this from the debugger, particularly with respect to this, so in this case you have to go to the actual code to see that you need to refer to _this in the debugger.

Old Pro
  • 24,624
  • 7
  • 58
  • 106
  • Unfortunately I've run out of time to award the bounty before testing this answer. Will report back when I have done so. – dwjohnston Dec 09 '18 at 23:25
  • This makes makes `this` be undefined - which isn't what I want - I want to refer to the React class instance. – dwjohnston Dec 10 '18 at 06:20
  • @dwjohnston I replaced the previous answer with a new one now that I have a different understanding of your question. Please accept this answer if it does, in fact, answer your question. – Old Pro Dec 12 '18 at 21:44
  • Thanks - I appreciate the effort you've gone to. Your example is what I'm trying to achieve (What it really is in the real world, pausing the thread and inspecting the state at a given time). It seems like the only way is to use the manual binding method - though I do have an idea that I'll post as an answer. – dwjohnston Dec 12 '18 at 23:24
  • @dwjohnston I don't understand what your issue is with this solution. You can just add `_this` (or `_this.props` and/or `_this.state` or even `this || _this`) as a watch expression or type it in the console to examine the component. – Old Pro Dec 13 '18 at 05:14
0

I think you need to set babel options to disable module processing. See this answer:

How to stop babel from transpiling 'this' to 'undefined'

In your .babelrc:

{
  "presets": [
    [ "es2015", { "modules": false } ]
  ]
}
Tiago Coelho
  • 5,023
  • 10
  • 17
0

An pragmatic alternative - if what you're wanting to do is inspect the state/props of a react component at a certain point, is to enter the break point as per normal - but instead of using the debugger to inspect the state - use the react dev tools plugin to actually examine the state.

This might prove to be a bit fiddly, but it's an option.

dwjohnston
  • 11,163
  • 32
  • 99
  • 194