Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding context when calling ES6 method. How to access object from within method called as callback?

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?

like image 447
rod Avatar asked Feb 21 '16 10:02

rod


2 Answers

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.

like image 75
Bergi Avatar answered Oct 18 '22 07:10

Bergi


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');
 }
 // ...
}
like image 44
Prasanth Avatar answered Oct 18 '22 08:10

Prasanth