4

I'm following the React Beginner Tutorial and I'm trying to translate it into ES6. However when I changed the CommentBox to an ES6 class it started giving me a this.props.url is undefined error (in the AJAX call in loadCommentsFromServer). I think this has something to do with how ES6 binds this, but this I'm not very familiar with the language (nor React) so I'm not sure. I've looked at the React 0.13 release notes and saw this:

React.createClass has a built-in magic feature that bound all methods to this automatically for you. This 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.

I'm not exactly sure but I thought that it meant I had to save the value of this (as in let that = this and .bind(that)) but that also gave the same this.props.url is undefined - I'm not sure where to go next.

Here's my current code:

class CommentBox extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: []
    };
  }
  loadCommentsFromServer() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({
          data: data
        })
      }.bind(this)
    });
  }
  handleCommentSubmit(comment) {
    var comments = this.state.data;
    var newComments = comments.concat([comment]);
    this.setState({ data: newComments });
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: function(data) {
        this.setState({ data: data });
      },
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  }
  componentDidMount() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  }
  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data}/>
        <CommentForm onCommentSubmit={this.handleCommentSubmit}/>
      </div>
    );
  }
};
nathanhleung
  • 506
  • 6
  • 14

3 Answers3

5

You need use bind(this) to bind your events. like below:

componentDidMount() {
    this.loadCommentsFromServer().bind(this);
    setInterval(this.loadCommentsFromServer.bind(this), this.props.pollInterval);
  }

You could read the reference from this link: https://facebook.github.io/react/docs/reusable-components.html#no-autobinding

No Autobinding Methods follow the same semantics as regular ES6 classes, meaning that they don't automatically bind this to the instance. You'll have to explicitly use .bind(this) or arrow functions =>.

Ryan.Ruan
  • 66
  • 3
  • and since I can't use arrow functions in this instance, I guess `bind(this)` is the only solution? – nathanhleung Sep 23 '15 at 02:53
  • I mad a mistake before that "this.loadCommentsFromServer()" cannot bind(this). – Ryan.Ruan Sep 24 '15 at 07:06
  • Btw, if you want to use arrow functions .the only way is like this:setInterval(()=>{ $.ajax({ url: this.props.url, dataType: 'json', cache: false, success: function(data) { this.setState({ data: data }) }.bind(this) });},this.props.pollInterval); – Ryan.Ruan Sep 24 '15 at 07:07
1

Within callback functions like success and error, the scope changes, so "this" is no longer the CommentBox.

You need to do something like:

handleCommentSubmit(comment) {
    var comments = this.state.data;
    var newComments = comments.concat([comment]);
    this.setState({ data: newComments });
    var comment_box = this;
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: function(data) {
        comment_box.setState({ data: data });
      },
      error: function(xhr, status, err) {
        console.error(comment_box.props.url, status, err.toString());
      }.bind(this)
    });
  }

Apply this to other applicable places in your code

Hieu Le
  • 738
  • 4
  • 8
  • It was working when it was written with ES5 and `React.createClass`, did anything in ES6 change in the way that `this` is bound to callback functions? – nathanhleung Sep 23 '15 at 02:22
  • I am not sure. I would recommend also adding console.log(this) within the callback functions so you can verify what "this" is. – Hieu Le Sep 23 '15 at 02:23
  • Ok, so `this` works once (it shows the right object) and then it starts giving me the undefined error – nathanhleung Sep 23 '15 at 02:23
  • Sorry can you be more specific about which part works once? And which part is giving you the undefined error again? is it loadCommentsFromServer() still? Let me know after you put console.log(this) within all callbacks. Did you apply my method above to loadCommentsFromServer() ? – Hieu Le Sep 23 '15 at 02:27
1

This is a behavior specific to the implementation of React.Component as an ES6 class. When used ES5 style, React components autobind all of their functions. When you use the ES6 class style, the only methods autobound are those specifically included in React.Component (render, componentDidMount, etc).

This is actually mentioned in the documentation, though it's easy to overlook.

And don't feel bad; I know it's in the documentation because I had to go looking for it the first time I ported some working React components into ES6 classes.

S McCrohan
  • 6,663
  • 1
  • 30
  • 39