1

I have been struggling with trying to migrate my React code from ES5 to ES6. As I have found out by now, this is no longer automatically bound which causes all sorts of hell.

I am trying to figure out by trial and error what objects are being passed around. So far I can find everything and adjust accordingly. However when it comes to this.setState I am having problems because it is not visible in console.log!!!! See screenshot in ES5:

enter image description here

and here is the same kind of code in ES6: enter image description here

Please teach me how to fish i.e. help me figure out how to understand when an object has this.setState or not?

things i have tried

  1. from googling around i understand you might be able to default bind everything by changing the base component. unfortunately this did not work when it came to this.setState. It looks identical to the ES5 version of this in console so I concluded that setState is still not being bound somehow
Community
  • 1
  • 1
swyx
  • 2,378
  • 5
  • 24
  • 39
  • Share your code here.. – Jyothi Babu Araja Jan 08 '17 at 13:28
  • When you pass your component functions to event handler e.g. onClick and such, its not bind to "this". So what you have to do is something like: onClick={this.yourmethod.bind(this)}. All the other stuff , like lifecycle methods will have this in place – Kinnza Jan 08 '17 at 13:38
  • printing out an object with functions doesnt always work cause they can be part of the prototype. Try to debug using the browser debug tools, its much easier then doing so via console.log... – Kinnza Jan 08 '17 at 13:39
  • 1
    When you say "unfortunately this did not work when it came to this.setState" what does this actually mean? You're absolutely required to `.bind(this)` on event handlers if you want to preserve the scope of `this`, which does "work" in ES6. –  Jan 08 '17 at 14:12
  • 2
    Just define your methods with `name = () => { /* stuff /* }`, doing so the method will be auto bound to the component context – Fez Vrasta Jan 08 '17 at 15:11
  • 1
    I think learning how `this` works would make your life easier in the long run, than chasing and inspecting every use of `this`. If you know how `this` works then it will be clear when and when not to bind a function. – Felix Kling Jan 08 '17 at 16:49
  • @FezVrasta you were right. I didn't understand this. @FelixKling yeah this is me asking for help with understanding `this`. I definitely didn't get the subtlety that when you call `this.props.method` then `this` refers to `this.props` instead of `this`. – swyx Jan 09 '17 at 19:27

4 Answers4

2

To oversimplify how this works in JS:

  • If you call a function as an object method (e.g., instance.foo()) then this refers to that object instance.
  • If you call a function by itself (e.g., foo()), then this is either undefined or the global object, depending on whether strict mode is in effect.
  • If you take a reference to an object method then call it, that means you're calling it by itself, even though it was originally an object method. (E.g., var bar = instance.foo; bar();.

Again, this is an oversimplification; MDN has details.

As this applies to React, and as explained in the React documentation on "Handling Events":

You have to be careful about the meaning of this in JSX callbacks. In JavaScript, class methods are not bound by default. If you forget to bind this.handleClick and pass it to onClick, this will be undefined when the function is actually called.

In your code, you render your RawInput as

 <RawInput value={this.state.value} updateValue={this.updateValue}/>

You're passing a reference updateValue function in as a simple function, so this will not be bound within updateValue.

Basically, any time you pass a function as a React prop, unless you've bound it yourself, it's likely an error. The symptom is typically that this is undefined. In your code, it's a little more complicated:

this.props.updateValue(modifiedValue);

The RawInput's updateValue property is the unbound function App.updateValue, but because you're invoking it as this.props.updateValue, it's being called as if it were a method of this.props - so this refers to the RawInput's props. That's why your console.log is showing an object with only two properties (start and updateValue): it isn't that setState isn't bound or went away, it's that updateValue wasn't bound, so this isn't what you expect within updateValue.

To fix the issue, as the React docs explain:

  • Use a fat arrow function: updateValue={(value) => this.updateValue(value)}
  • Use the experimental property initializer syntax: Replace updateValue(modifiedValue) {...} with updateValue = (modifiedValue) => {...}.
  • Not mentioned in the React docs, but an approach I often use: Bind updateValue yourself. For example:
constructor(props) {
    super(props);
    this.updateValue = this.updateValue.bind(this);
}
Josh Kelley
  • 56,064
  • 19
  • 146
  • 246
  • Josh, I can't thank you enough. You even hunted down my pen to go in depth. just amazing. i confess I'm one of those dense people that just nods along to the docs and thinks he understands it until I actually run into the issue and I learn by sheer banging my head into a wall. thank you again. used the fat arrow. I tried a generalized binding method found somewhere else on SO (hence using BaseComponent instead of React.Component) but it did not work in this particular case probably cos of the nesting. – swyx Jan 09 '17 at 19:26
1

you can replace console.log with this:

console.shallowCloneLog = function(){
    var typeString = Function.prototype.call.bind(Object.prototype.toString)
    console.log.apply(console, Array.prototype.map.call(arguments, function(x){
        switch (typeString(x).slice(8, -1)) {
            case 'Number': case 'String': case 'Undefined': case 'Null': case 'Boolean': return x;
            case 'Array': return x.slice();
            default:
                var out = Object.create(Object.getPrototypeOf(x));
                out.constructor = x.constructor;
                for (var key in x) {
                    out[key] = x[key];
                }
                Object.defineProperty(out, 'constructor', {value: x.constructor});
                return out;
        }
    }));
}

any way, regarding your question, you can add a method like this:

updateValue = () => {...}

in m POV - es6 is cool and great. React components by es6' classes are useless. stick with createClass and you'll be fine (and have mixins if you want!)

yonatanmn
  • 1,590
  • 1
  • 15
  • 21
  • hey, marked Josh right as he gave an insanely comprehensive answer. this console log thing is really cool but its probably more than I need right now. Your answer was still right. Im not sure what you mean with the last part - you think es6 is cool but you dont recommend using React with es6? – swyx Jan 09 '17 at 19:30
  • I recommend using es6 in react for everything except for `classes`. greater JS developers than me also recommends that. – yonatanmn Jan 10 '17 at 15:52
  • got it. this seems to be what codepen works best with too. thanks again – swyx Jan 12 '17 at 21:43
1

Try Object.prototype.hasOwnProperty(). For example:

var X = function() {};
X.prototype.setSomething = 'a';
var x = new X();
x.setSomething; // log 'a' here
x.hasOwnPrperty('setSomething') // log false here

In your case, just console.log(this.hasOwnProperty('setState')).

Timathon
  • 1,049
  • 9
  • 11
  • thanks Timathon ,turns out my `this` was just plain not what i thought it was. thanks again. – swyx Jan 09 '17 at 19:31
1

You have to bind your updateValue function with the component in order to have the correct context (this).

In your case, your parent class BaseComponent allows you to use the herited method _bind like that :

class App extends BaseComponent {
  constructor(props){
    super(props);
    this.state={value:'start'};
    this._bind('updateValue');
...
Freez
  • 7,208
  • 2
  • 19
  • 29