TheoG
TheoG

Reputation: 1558

React.Component Onclick event: Comments.js:189 Uncaught TypeError: Cannot read property 'removeCommentMutation' of undefined

So, I wish to call a function using an onClick event and pass it some parameters from the event, but am receiving the above mentioned error message.

What am I overlooking here?

My code is as follows:

class Comments extends React.Component {

  constructor(props) {
    super(props);
    this.removeCommentMutation = this.removeCommentMutation.bind(this);
  }
  
  removeCommentMutation (postID, commentID) {
  ....
  }

  handleSubmitError (err) {
    console.error(err.message);
  }

  renderComment (comments) {
    return (
      <div className="comment" key={comments.id}>
        <p>
          <strong>{comments.user}</strong>
          {comments.text} 
          <button className="remove-comment" onClick={() => this.removeCommentMutation(this.props.postId, comments.id)}>&times;</button>
        </p>
      </div>
    );
  }
  handleSubmit (e) {
    e.preventDefault();
    this.addCommentMutation(this.props.postId, this.refs.author.value, this.refs.comment.value);
    this.refs.commentForm.reset();
    this.refs.author.focus();
  }

  render () {
    const comments = this.props.post.comments || [];
    const currentPosts = comments.map(this.renderComment);

    return (
      <div className="comments">

        {currentPosts}

        <form onSubmit={this.handleSubmit} ref="commentForm" className="comment-form">
          <input type="text" ref="author" placeholder="author"/>
          <input type="text" ref="comment" placeholder="comment"/>
          <input type="submit" hidden/>
        </form>
      </div>
    );
  }
};

}

The full error is:

Comments.js:189 Uncaught TypeError: Cannot read property 'removeCommentMutation' of undefined
    at onClick (http://localhost:7770/static/bundle.js:36072:30)
    at HTMLUnknownElement.wrapped (http://localhost:7770/static/bundle.js:63526:29)
    at Object.ReactErrorUtils.invokeGuardedCallback (http://localhost:7770/static/bundle.js:43148:16)
    at executeDispatch (http://localhost:7770/static/bundle.js:70629:21)
    at Object.executeDispatchesInOrder (http://localhost:7770/static/bundle.js:70652:5)
    at executeDispatchesAndRelease (http://localhost:7770/static/bundle.js:7436:22)
    at executeDispatchesAndReleaseTopLevel (http://localhost:7770/static/bundle.js:7447:10)
    at Array.forEach (native)
    at forEachAccumulated (http://localhost:7770/static/bundle.js:44180:9)
    at Object.processEventQueue (http://localhost:7770/static/bundle.js:7652:7)
    at runEventQueueInBatch (http://localhost:7770/static/bundle.js:74532:18)
    at Object.handleTopLevel [as _handleTopLevel] (http://localhost:7770/static/bundle.js:74548:5)
    at handleTopLevelWithoutPath (http://localhost:7770/static/bundle.js:74651:24)
    at handleTopLevelImpl (http://localhost:7770/static/bundle.js:74631:3)
    at ReactDefaultBatchingStrategyTransaction.perform (http://localhost:7770/static/bundle.js:12582:20)
    at Object.batchedUpdates (http://localhost:7770/static/bundle.js:42559:19)
    at Object.batchedUpdates (http://localhost:7770/static/bundle.js:2907:20)
    at dispatchEvent (http://localhost:7770/static/bundle.js:74762:20)
    at HTMLDocument.wrapped (http://localhost:7770/static/bundle.js:63526:29)

Upvotes: 0

Views: 78

Answers (1)

Dan
Dan

Reputation: 8794

You need to bind all of your functions. In React, the context of this is undefined. That is until you use bind() to change the context to be your Component.

You can do this one of two ways.

Using .bind()

class Comments extends React.Component {

  constructor(props) {
    super(props);
    this.removeCommentMutation = this.removeCommentMutation.bind(this);
    this.handleSubmitError = this.handleSubmitError.bind(this);
    this.renderComment = this.renderComment.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  removeCommentMutation (postID, commentID) {
  ....
  }

  handleSubmitError (err) {
    console.error(err.message);
  }

  renderComment (comments) {
    return (
      <div className="comment" key={comments.id}>
        <p>
          <strong>{comments.user}</strong>
          {comments.text} 
          <button className="remove-comment" onClick={() => this.removeCommentMutation(this.props.postId, comments.id)}>&times;</button>
        </p>
      </div>
    );
  }
  handleSubmit (e) {
    e.preventDefault();
    this.addCommentMutation(this.props.postId, this.refs.author.value, this.refs.comment.value);
    this.refs.commentForm.reset();
    this.refs.author.focus();
  }

  render () {
    const comments = this.props.post.comments || [];
    const currentPosts = comments.map(this.renderComment);

    return (
      <div className="comments">

        {currentPosts}

        <form onSubmit={this.handleSubmit} ref="commentForm" className="comment-form">
          <input type="text" ref="author" placeholder="author"/>
          <input type="text" ref="comment" placeholder="comment"/>
          <input type="submit" hidden/>
        </form>
      </div>
    );
  }
};

}

Using Arrow functions - ES6

Typically the context of this in JavaScript is determined by how a function is called. With arrow functions, the context is lexical, meaning that this is determined by the outer scope (which in this case is your Comments component).

You can use arrow functions like so, which would mean you don't have to constantly bind() every single method.

class Comments extends React.Component {

  constructor(props) {
    super(props);
  }

  removeCommentMutation = (postID, commentID) => {
  ....
  }

  handleSubmitError = (err) => {
    console.error(err.message);
  }

  renderComment = (comments) => {
    return (
      <div className="comment" key={comments.id}>
        <p>
          <strong>{comments.user}</strong>
          {comments.text} 
          <button className="remove-comment" onClick={() => this.removeCommentMutation(this.props.postId, comments.id)}>&times;</button>
        </p>
      </div>
    );
  }
  handleSubmit = (e) => {
    e.preventDefault();
    this.addCommentMutation(this.props.postId, this.refs.author.value, this.refs.comment.value);
    this.refs.commentForm.reset();
    this.refs.author.focus();
  }

  render () {
    const comments = this.props.post.comments || [];
    const currentPosts = comments.map(this.renderComment);

    return (
      <div className="comments">

        {currentPosts}

        <form onSubmit={this.handleSubmit} ref="commentForm" className="comment-form">
          <input type="text" ref="author" placeholder="author"/>
          <input type="text" ref="comment" placeholder="comment"/>
          <input type="submit" hidden/>
        </form>
      </div>
    );
  }
};

}

Notice the foo = () => {} syntax. You don't have to do this for Component lifecycle methods e.g. componentWillMount, componentDidMount and you don't have to do this for render.

Upvotes: 2

Related Questions