14

I'm trying to wrap my head around the syntax for Classes in ES6. While at the same time learning Fabric native through Bonnie Eisenman's Learning React Native.

I've come accross an issue around accessing this in a callback, when that callback is a Class "method". I'm aware issues around lexical this in callbacks have been raised many times on StackOverflow. For instance in How to access the correct `this` context inside a callback?.

Based on my research online I've come across a solution. But I am not sure this is the correct way to do it in ES6.

My issue came about when I tried the following below:

class WeatherProject extends Component {
  constructor(props) {
    super(props);
    this.state = {
      zip: ''
    };
  }

  _handleTextChange(event) {
    console.log(event.nativeEvent.text);
    this.setState({zip: event.nativeEvent.text})
  }

  render() {
    return (
      <TextInput
        style={styles.input}
        onSubmitEditing={this._handleTextChange}/>
    );
  }
}

(Which I've only slightly modified from the example in the book to match the ES6 class syntax and the import/export syntax instead of Require.)

If I do this, the this in _handleTextChange is undefined (cannot read property 'setState' of undefined). I was surprised by this. Coming from other OO languages, I'm interpreting as though this method is behaving more as if it was a static method.

I've been able to solve this by skipping the class method and using the arrow notation. onSubmitEditing={event => this.setState({name: event.nativeEvent.text})}. Which works fine. I have no problems or confusion with that.

I really want to work out how to call a class method though. After a fair bit of research I've managed to make it work by doing the following: onSubmitEditing={this._handleTextChange.bind(this)}. Perhaps I've misunderstood a fundamental aspect of JavaScript (I'm a beginner in JS), but this seems completely insane to me. Is there really no way of accessing the context of an object from within a method without explicitly binding the object back onto... it's own method, at the point where it is called?

I've also tried adding var self = this; in the constructor and calling self.setState in _handleTextChange. But wasn't too surprised to find that didn't work.

What's the correct way of accessing an object from within one of its methods when it is called as a callback?

Community
  • 1
  • 1
rod
  • 3,403
  • 4
  • 19
  • 25
  • All the possible ways are outlined in the question you linked to. One could argue that the most correct one is the one that expresses your intentions most clearly. – Felix Kling Feb 21 '16 at 15:56

3 Answers3

18

React.createClass (ES5) way of creating class has a built-in feature that bound all methods to this automatically. But while introducing classes in ES6 and migrating React.createClass, They found it can be a little confusing for JavaScript developers that are not used to this feature in other classes, or it can be confusing when they move from React to other classes.

So, they decided not to have this built-in into React's class model. You can still explicitly prebind methods in your constructor if you want like

class WeatherProject extends Component {
  constructor(props) {
    super(props);
    this.state = {
      zip: ''
    };
    this._handleTextChange = this._handleTextChange.bind(this); //Binding to `this`
  }

  _handleTextChange(event) {
    console.log(event.nativeEvent.text);
    this.setState({zip: event.nativeEvent.text})
  }

But we always have a simple way to avoid this prebinding. Yeah! You got it. Arrow functions.

class WeatherProject extends Component {
  constructor(props) {
    super(props);
    this.state = {
      zip: ''
    };
  }

  _handleTextChange = event => {
    console.log(event.nativeEvent.text);
    this.setState({zip: event.nativeEvent.text})
  }

  render() {
    return (
      <TextInput
        style={styles.input}
        onSubmitEditing={this._handleTextChange}/>
    );
  }
}

BTW, This is all regarding React. ES6 class always has a way of accessing the context of an object from within a method without explicitly binding the object back onto it's own method.

class bindTesting {
  constructor() {
    this.demo = 'Check 1';
  }

  someMethod() {
    console.log(this.demo);
  }

  callMe() {
    this.someMethod();
  }
}

let x = new bindTesting();
x.callMe(); //Prints 'Check 1';

But this doesn't prints 'Check 1' if we call it in JSX Expression.

EDIT :: As @Oka mentioned, arrow functions in class bodies is ES7+ feature and available in Compiler/polyfills like babel. If you're not using transpiler that support this feature, We can just bind to this as mentioned above or write a new BaseComponent like this ( Which is a bad idea )

class BaseComponent extends React.Component {
 _bind(...methods) {
  methods.forEach( (method) => this[method] = this[method].bind(this) );
 }
}

class ExampleComponent extends BaseComponent {
 constructor() {
  super();
  this._bind('_handleTextChange', '_handleClick');
 }
 // ...
}
Prasanth
  • 418
  • 4
  • 8
  • 2
    Arrow functions in `class` bodies is an ES7 proposal, and is considered _experimental_ in transpilers such as Babel. – Oka Feb 21 '16 at 11:56
  • @gunshot Changing the variable to access the method within the constructor, so as to be a version with the object context correctly bound is exactly the type of solution I was looking for but couldn't work out for myself. Thank you. I'll use ```this._handleTextChange = this._handleTextChange.bind(this); ```. Ingenious! – rod Feb 21 '16 at 23:52
6

Breaking away from ES6, and React for a moment, in regular old JavaScript, when you pass an object's method around it is just a reference to the function itself, not the object and the function.

Any invoking function can choose to use the implicit this, by calling the function normally, or it can even choose to change the context using .call and .apply, or .bind.

var O = {
  foo: function () {
    console.log(this);
  },
  bar: 51
};

O.foo(); // `this` is O, the implicit context, inferred from the object host

var foo = O.foo;

foo(); // `this` is is the global object, or undefined in Strict Mode

What is happening is, you are passing the _handleTextChange function to an event emitter, which gets executed some time later. The event emitter has no idea that it received the function as a method. It just executes it.

var O = {
  foo: function (event) {
    console.log(this);
    console.log(event);
  },
  bar: 51
};

function invoke (func) { // the function reference held by O.foo is now held by `func`
  func('some param'); // it has no idea as to what the contextual `this` is bound to, maybe its implicitly global, maybe its explicitly bound, maybe its undefined
}

invoke(O.foo);

invoke(O.foo.bind(O)); // A copy of the function with the context explicitly bound

Take a look at context sharing:

function foo () {
  console.log(this.a);
}

// Both object hold references to the foo function
var O = { foo : foo, a : 5 },
    O2 = { bar : foo, a : 1 };

O.foo(); // implicit `this` from O
O2.bar(); // implicit `this` from O2

var Z = { a : 23 };

O.foo.call(Z); // explicit `this` from Z

You can get arbitrarily complicated with these examples.

Oka
  • 23,367
  • 6
  • 42
  • 53
6

Perhaps I've misunderstood a fundamental aspect of JavaScript (I'm a beginner in JS), but this seems completely insane to me. Is there really no way of accessing the context of an object from within a method without explicitly binding the object back onto... it's own method, at the point where it is called?

Well, "methods" are not owned by objects in javascript. All functions are first-class values (objects), and not "part" of an object or a class.

The binding of the this value happens when the function is called, depending on how the function is called. A function call with method notation has its this set to the receiving object, an event listener call has its this set to the current event target, and a normal function call only has undefined.

If you are accessing a method on an instance, it's really just the standard prototype inheritance property access which yields a function, which is then called and dynamically-bound to the receiver.

If you want to call a function as a method on an instance when it is an event listener, then you need to explicitly create a function that does this - the "bound" method. In ES6, the arrow notation is usually preferred for this.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375