Reputation: 1288
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>
);
}
}
Upvotes: 1
Views: 931
Reputation: 57944
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 tomap
, it will be passed to callback when invoked, for use as its this value. Otherwise, the value undefined will be passed for use as itsthis
value. (emphasis mine)
Where thisArg
is an optional argument of map
, see Array.prototype.map
Syntax. That means, when you do:
{this.props.questions.map(this.addQuestion)}
this
context is undefined when calling this.addQuestion
, thus it is undefined in the call to 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 mentioned earlier, this
is undefined, you are actually trying to do:
undefined.addQuestion.bind(undefined, question)
which throws the error because undefined
has no function addQuestion
.
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
from the component. An alternative and functionally 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. That means, in the call to addQuestion
, the component is this
, like the above solution.
Another thing I'd recommend is that, instead of creating a new function every time with bind
, do it once in the constructor:
constructor(/* props if needed */) {
/* super(props) if needed */
this.addQuestion = this.addQuestion.bind(this);
}
That way, the bound method is already this.addQuestion
. You can then get rid of all your bind(this)
s, but keep your bind(this, question)
, as you bind with an argument.
Upvotes: 1
Reputation: 11206
I believe the proper way to do this in React is to bind this in the constructor.
constructor(props) {
super(props)
this.askQuestion = this.askQuestion.bind(this)
}
Upvotes: 2