Mjuice Mjuice - 1 month ago 5
Javascript Question

React Component's onClick handler not binding to "this"

I have a react component in which I am trying to pass a span tag an onClick event handler. The event handler is called "askQuestion". I bind the onClick event handler to the context of the component with the .bind(this, parameter) function.

Despite my attempt to bind to "this" I'm still getting an error in the dev tools console saying "cannot read property askQuestion of undefined." I'm pretty sure this error means that askQuestion is not bound properly to the context of the component. I need help binding askQuestion properly.

Here is the component:

class Questions extends Component {
askQuestion(question) {
alert("hello");
}

addQuestion(question, index) {
return (
<div key={index} className="col-xs-12 col-sm-6 col-md-3">
<span onClick={this.askQuestion.bind(this, question)}>
{question.q}
</span>
</div>
);
}
render() {
return (
<div id="questions" className="row">
<h2>Questions</h2>
{this.props.questions.map(this.addQuestion)}
</div>
);
}


}

Answer

Explanation

The problem is that you use Array.prototype.map, which does not bind this unless explicitly told to, and the context of the callback is, in turn, undefined. From the documentation:

If a thisArg parameter is provided to map, it will be passed to callback when invoked, for use as its this value. Otherwise, the value undefined will be passed for use as its this value. (emphasis mine)

That means, when you do:

{this.props.questions.map(this.addQuestion)}

this context is undefined when calling this.addQuestion, thus it is undefined in addQuestion. Let me illustrate the problem further by taking a look at your addQuestion method:

addQuestion(question, index) {
  return (
    <div key={index} className="col-xs-12 col-sm-6 col-md-3">
      <span onClick={this.askQuestion.bind(this, question)}>
        {question.q}
      </span>
    </div>
  );
}

Here, since as mention earlier that this is undefined here, you are actually trying to do:

undefined.askQuestion.bind(undefined, question)

which throws the error because undefined has no function askQuestion.

Solution

Again, from the documentation:

Syntax

var new_array = arr.map(callback[, thisArg])

thisArg

Optional. Value to use as this when executing callback.

You can see that we can explicitly pass a this context to map, to be used as this context in the callback. That means we can pass an additional argument to be this in the function. This can be applied like so:

{this.props.questions.map(this.addQuestion, this)}

Since this refers to the actual component here, the component is passed as this. That will then correctly call the method addQuestion. An alternative and equivalent way to do is like so:

{this.props.questions.map(this.addQuestion.bind(this))}

Again, since this refers to the actual component here, the component is bound as the this context of the method, appropriately calling the method.