1

On my way to learn React and ES6, I took the official tutorial and played around to make it ES6 compatible. But when it comes to executing the Ajax request, I get the following error:

CommentBox.js:23 Uncaught TypeError: Cannot read property 'url' of undefined

Here is my CommentBox file/code:

import React from 'react';
import CommentList from './CommentList.js';
import CommentForm from './CommentForm.js';

export default class CommentBox extends React.Component {
  constructor(props) {
    super(props);
    console.log(this.props)
    this.state = {
      data: []
    }
  }

  loadCommentsFromServer() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  }

  handleCommentSubmit(comment) {
    let comments = this.state.data;

    // Optimistically set  id on the new comment.
    // It will be replaced by an id generated by the server.
    // In a production you would have a more robust system in place.
    comment.id = Date.now();
    let 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});
      }.bind(this),
      error: function(xhr, status, err) {
        this.setState({data: comments});
        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>
    );
  }
}

The error happen on loadCommentsFromServer; it seems not to know what this.props is. I thought it was a this reference problem and found a similar question where it is suggested to use new ES6 arrows to fix the issue. I then tried: loadCommentsFromServer = () => {}, but Browserify complains and doesn't build.

Community
  • 1
  • 1
Giorgia Sambrotta
  • 1,133
  • 1
  • 15
  • 45
  • Possible duplicate of [How to access the correct \`this\` / context inside a callback?](http://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-context-inside-a-callback) – Felix Kling Mar 31 '16 at 15:28
  • See "Common problem: Using object methods as callbacks / event handlers" in the suggested duplicate. – Felix Kling Mar 31 '16 at 15:29
  • You need to bind it manually. ES6 class doesn't autobind – thangngoc89 Mar 31 '16 at 15:33

2 Answers2

6

This line is your problem:

setInterval(this.loadCommentsFromServer, this.props.pollInterval);

You are passing the method loadCommentsFromServer to setInterval, but it will be invoked without context therefore this will no longer bound to your component (this will be bound to the window). Instead you should do something like this:

setInterval(this.loadCommentsFromServer.bind(this), this.props.pollInterval);
// OR... use a fat arrow, which preserves `this` context
setInterval(() => this.loadCommentsFromServer(), this.props.pollInterval); 

Finally, you should store the interval id, so that you can clear it when your component is unmounted:

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

componentWillUnmount() {
   clearInterval(this.intervalId);
}
WickyNilliams
  • 5,218
  • 2
  • 31
  • 43
  • thank! That was the problem, very clear explanation too – Giorgia Sambrotta Mar 31 '16 at 15:42
  • If you use class properties and define `loadCommentsFromServer` as an arrow function you don't actually need either of these code changes at `setInterval`. – Aaron Beall Mar 31 '16 at 17:17
  • @Aaron Personally I do not recommend very early stage language features on SO (unless the question is specifically about them). It adds to complexity in fixing the problem, and if the features doesn't become standardised the answer becomes useless in future – WickyNilliams Jun 02 '16 at 08:43
1

I thought it was a this reference problem and googling around i found a similar stackoverflow question where they suggest to use new ES6 arrows to fix the issue. I try then to use: loadCommentsFromServer = () => {} but browserify complain and don't build :/

ES6 arrow functions will indeed solve your problem. The build error is because ES6 does not support class properties, and loadCommentsFromServer = is a class property initializer. Enable ES7 class properties and you should be able to do this and it will solve your problem without needing to manually bind or wrap your callback in a new fat arrow function when you call setInterval.

Aaron Beall
  • 49,769
  • 26
  • 85
  • 103