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?
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.
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');
}
// ...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With