Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I have to .bind(this) for methods defined in React component class, but not in regular ES6 class

Something that is puzzling me is why when I define a react component class, values contained in the this object are undefined in methods defined (this is available in lifecycle methods) within the class unless I use .bind(this) or define the method using an arrow function for example in the following code this.state will be undefined in the renderElements function because I did not define it with an arrow function and did not use .bind(this)

class MyComponent extends React.Component {
    constructor() {
        super();
        this.state = { elements: 5 }
    }

    renderElements() {
        const output = [];

        // In the following this.state.elements will be undefined
        // because I have not used  .bind(this) on this method in the constructor
        // example: this.renderElements = this.renderElements.bind(this)
        for(let i = 0; i < this.state.elements; i ++){
            output.push(<div key={i} />);
        }

        return output;
    }

    // .this is defined inside of the lifecycle methods and 
    // therefore do not need call .bind(this) on the render method.
    render() {
        return (
            <div onClick={this.renderElements}></div>
        );
    }
}

Then in the following example I do not need to use .bind(this) or an arrow function, this is available as expected in speak function

class Animal { 
    constructor(name) {
        this.name = name;
    }
    
    speak() {
        console.log(this.name + ' makes a noise.');
    }
}

class Dog extends Animal {
    speak() {
        console.log(this.name + ' barks.');
    }
}

var d = new Dog('Mitzie');
d.speak();

http://jsbin.com/cadoduxuye/edit?js,console

To clarify, my question is two part. One) why in the second code example do I not need to call .bind(this) to the speak function, but I do in the React component for the renderElements function and Two) why do the lifecycle methods (render, componentDidMount, etc) already have access to the class' this object, but renderElements does not.

In the React docs it says the following

[React Component Class] Methods follow the same semantics as regular ES6 classes, meaning that they don't automatically bind this to the instance.

But clearly they do, as the second code example I've posted shows.

Update

Both links in the first two comments show a working example of React classes NOT using .bind(this) on class methods and it works fine. But still in the docs is explicitly says you need to bind your methods, or use an arrow function. In a project using gulp and babel I can reproduce. Could it mean browsers have updated things?

Update 2

My initial code example had this.renderElements() called directly in the render function. That would work as expected without binding the function, or defining it with an arrow function. The issue occurs when I put the function as an onClick handler.

Update 3

The issue occurs when I put the function as an onClick handler.

In fact it is not an issue at all. The context of this changes when passed to the onClick handler, so that's just how JS works.

like image 777
pizzarob Avatar asked Sep 17 '16 23:09

pizzarob


People also ask

Why do we need bind this in React class?

When we bind the this of the event handler to the component instance in the constructor, we can pass it as a callback without worrying about it losing its context. Arrow functions are exempt from this behavior because they use lexical this binding which automatically binds them to the scope they are defined in.

What does .bind do in React?

The bind() is an inbuilt method in React that is used to pass the data as an argument to the function of a class based component.

Will ES6 classes bind the instance automatically?

[React Component Class] Methods follow the same semantics as regular ES6 classes, meaning that they don't automatically bind this to the instance.

Why do class methods need to be bound to a class instance?

This is because whenever inside a class component when we need to pass a function as props to the child component, we have to do one of the following: Bind it inside the constructor function. Bind it inline (which can have some performance issues).


4 Answers

The value of this primarily depends on how the function is called. Given d.speak();, this will refer to d because the function is called as an "object method".

But in <div>{this.renderElements}</div> you are not calling the function. You are passing the function to React which will call it somehow. When it is called, React doesn't know which object the function "belonged" to so it cannot set the right value for this. Binding solves that

I actually think what you really want is

<div>{this.renderElements()}</div> //         call function ^^ 

i.e call the function as an object method. Then you don't have to bind it.


Have a look at MDN to learn more about this.

like image 54
Felix Kling Avatar answered Sep 22 '22 17:09

Felix Kling


Event handlers in the component will not be bound automatically to the component instance like other methods ( life cycle methods...).

class MyComponent extends React.Component {    render(){       return (          <div onClick={this.renderElements}>              {this.renderElements()} <-- `this` is still in side the MyComponent context          </div>       )    } } //under the hood  var instance = new MyComponent(); var element = instance.render(); //click on div element.onClick() <-- `this` inside renderElements refers to the window object now 

Check this example to understand this more :

class Animal {      constructor(name) {         this.name = name;     }      speak() {         console.log(this.name + ' makes a noise.');     }   }  class Dog extends Animal {     run(){        console.log(this.name + ' runs');     }     speak() {         console.log(this.name + ' barks.');         this.run(); <-- `this` is still in the Dog context         return {onRun : this.run};     } }  var d = new Dog('Mitzie'); var myDog = d.speak(); myDog.onRun() <-- `this` is now in the global context which is the `window` object 

You can check this article for more information.

like image 26
Phi Nguyen Avatar answered Sep 20 '22 17:09

Phi Nguyen


Functions in ES6 Classes - the case is explained very well by @Felix Kling. Every time you call a function on an object, this points to the object.

Lifecycle methods in React.Component - whenever React instantiates your component like myComponent = new MyComponent() it knows which object to call the lifecycle methods on, namely myComponent. So a simple call myComponent.componentDidUpdate() makes this available in the componentDidUpdate lifecycle method. Same for the other lifecycle methods.

Handlers & Bound in React.Component - this.state is undefined, because this is actually window - log it and see. The reason is that React invokes handlers on the global context, unless you have the handler bound to another context which overrides window (see @Phi Nguyen's answer also). I think they have done that to allow you more flexibility, because in complex applications your handler may come from another component passed through props and then you would like to have the possibility to say: "Hey, React - this is not my component, but it's parent."


React's documentation is a bid misleading when it says

Methods follow the same semantics as regular ES6 classes, meaning that they don't automatically bind this to the instance.

What they mean is that

var dog = new Dog('Mitzie');
speak = d.speak;

dog.speak() // this will be dog, because the function is called on dog
speak() // this will be window, and not dog, because the function is not bound
like image 27
Lyubomir Avatar answered Sep 24 '22 17:09

Lyubomir


1. Arrow Functions:

An arrow function expression has a shorter syntax compared to function expressions and lexically binds the this value (does not bind its own this, arguments, super, or new.target). Arrow functions are always anonymous. These function expressions are best suited for non-method functions and they can not be used as constructors.

Function.prototype.bind():

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

2.Component Specs and Lifecycle

To be absolutely clear: Most lifecycle methods are not bound, but called on an instance using the dot notation (true for componentWillMount, componentWillUnmount, componentWillReceiveProps and so on...), except componentDidMount which is bound to the instance since it gets enqueued into the transaction.

like image 34
Md.Estiak Ahmmed Avatar answered Sep 21 '22 17:09

Md.Estiak Ahmmed