Sid
Sid

Reputation: 523

Why the scoping of this keyword is invalid in JSX but works in ES6? (Lexical this vs arrow functions)

I have the react component below. Why this is not defined when changeNameTwo is called?

See jsbin: http://jsbin.com/nuhedateru/edit?js,output

Then why it works in a typical ES6 Class? See jsbin: http://jsbin.com/kiyijuqiha/edit?js,output

class HelloWorldComponent extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      name : 'yolo'
    }
  }

  changeName = () => {
    this.setState({name: 'another yolo'});
  }

  changeNameTwo() {
    this.setState({name: 'another yolo'});
  }

  render() {
    return (      
      <div>
        <h1>Hello {this.props.name}</h1>
        <p>Name: {this.state.name}</p>
        <button onClick={this.changeName}>Change Name</button>
        <button onClick={this.changeNameTwo}>Change Name 2</button>
      </div>
    );
  }
}

React.render(
  <HelloWorldComponent name="ES2015/ES6"/>,
  document.getElementById('react_example')
);

Upvotes: 0

Views: 298

Answers (2)

Dmitriy Nevzorov
Dmitriy Nevzorov

Reputation: 6078

When you use extend Component instead of React.createClass you lose autobinding feature. Binding to this is not a class itself, it’s undefined. It’s default JavaScript behavior and is quite expected.

What you can do about it

Method 1. Use Function.prototype.bind().

export default class CartItem extends React.Component {
    render() {
        <button onClick={this.increaseQty.bind(this)} className="button success">+</button>
    }
}

Method 2. Use a function defined in the constructor.

export default class CartItem extends React.Component {

    constructor(props) {
        super(props);
        this.increaseQty = this.increaseQty.bind(this);
    }

    render() {
        <button onClick={this.increaseQty} className="button success">+</button>
    }
}

Consider using this method over Method 1. Every time you use bind it actually creates a copy of a function. So it's much better to use it once in constructor then every time when your component calls render method

Method 3. Use an arrow function in the constructor.

export default class CartItem extends React.Component {

    constructor(props) {
        super(props);
        this.increaseQty = () => this.increaseQty();
    }

    render() {
        <button onClick={this.increaseQty} className="button success">+</button>
    }
}

Method 4. Use an arrow function and the class properties proposal.

export default class CartItem extends React.Component {

    increaseQty = () => this.increaseQty();

    render() {
        <button onClick={this.increaseQty} className="button success">+</button>
    }
}

Class properties are not yet part of current JavaScript standard. But your are free to use them in Babel using corresponding experimental flag (stage 0 in our case).

You can see original article here

Upvotes: 2

nanobar
nanobar

Reputation: 66355

One is a DOM event, the other you are directly calling. By default the context of a click event is the element that was clicked.

It is often desirable to reference the element on which the event handler was fired, such as when using a generic handler for a set of similar elements.

When attaching a handler function to an element using addEventListener(), the value of this inside the handler is a reference to the element. It is the same as the value of the currentTarget property of the event argument that is passed to the handler. (MDN - https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)

Your methods have a default context of the instance, but only if the context is not specified (a normal function call as opposed to a .call or .apply. Thus animal.speak() will by default have the correct context.

When a function is called as a method of an object, its this is set to the object the method is called on. (MDN - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)

To summarise, the click event sets a specific context which overrides that. As you probably know you can solve it with .call/.bind/.apply or onClick={(e) => this.changeName(e)}. Probably an implementation today wouldn't do that, but I imagine they have to keep it for compatibilities' sake.

Upvotes: 3

Related Questions