realseanp realseanp - 2 months ago 8
Javascript Question

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?

Answer

render method is called in the global context so all this.functionName inside return block of the render method will refer to the window object.

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.