I'm having difficulty understanding why create-react-app
is unable to compile, telling me that error 'updateWord' is not defined no-undef
. I'm fairly new to React with ES6. Normally I would write a component like const App = React.createClass({ });
but I've decided to try out some syntactical sugar instead.
I have parent component App
and a child component Input
:
class App extends Component {
constructor(props) {
super(props);
// all other code omitted...
}
handleInput(input) {
console.log(`handle: ${input}`);
updateWord(input);
// this also causes an error
// this.updateWord(input);
}
updateWord(input) {
console.log(`update ${input}`);
// why isn't this defined?
}
render() {
return (
<div className="App">
<Input onInput={this.handleInput} />
</div>
);
}
}
class Input extends Component {
handleInput(e) {
let input = e.target.value;
this.props.onInput(input);
}
render() {
return (
<form>
<input onChange={this.handleInput.bind(this)} />
</form>
);
}
}
I've tried changing to this.updateWord(input);
instead of updateWord(input)
but to no avail. I get:
"App.js:55 Uncaught TypeError: this.updateWord is not a function"
Normally when I implement a similar pattern (with ES5) to what I'm doing now I have no difficulties. For example:
const App = React.createClass({
getInitialState: function() {
// all other code omitted...
},
handleInput: function(input) {
console.log(`handle: ${input}`);
this.updateWord(input);
},
updateWord: function(input) {
console.log(`update ${input}`);
// In theory, this implementation would not cause any errors?
},
render: function() {
return (
<div className="App">
<Input onInput={this.handleInput} />
</div>
);
}
}
The problem is that when you do this.updateWord(...)
in this.handleInput
, this
refers to the Input
component. Let me illustrate the problem:
When you set the onInput
handler, like so:
onInput={this.handleInput}
Here, since your Input
component is calling the function, this
refers to the Input
component. This is due to the line:
this.props.onInput(input);
The Input
component is calling handleInput
. That means, in your handleInput
function, the this
context is Input
. Consider the line:
this.updateWord(input);
in the handleInput
function. Here you call this.updateWord
, but since this
is Input
, it tries to call updateWord
from Input
which does not exist, thus throwing the error.
The solution is to explicitly bind the this
context as the class (App
component) instead of the Input
component, using either Function.prototype.bind
or an arrow function. From the documentation:
The
bind()
method creates a new function that, when called, has itsthis
keyword set to the provided value
You can apply it like so:
onInput={this.handleInput.bind(this)}
Or more preferably in the constructor:
this.handleInput = this.handleInput.bind(this);
With the second option you may then do:
onInput={this.handleInput}
(This is more preferable as binding in the render
method will create a new function every time on render, which isn't preferred).
The this
context in the line above is the class. Since you bind this
, the class will be correctly used as this
context in the function and executing this.updateWord
will invoke the method in the class.
An even more preferable way is to use arrow functions instead of regular ES6 methods. From the documentation:
An arrow function expression has a shorter syntax compared to function expressions and does not bind its own
this
,arguments
,super
, ornew.target
.
We can apply this by assigning handleInput
to an arrow function instead of a regular method:
handleInput = (input) => {
console.log(`handle: ${input}`);
this.updateWord(input);
}
This will eliminate the use of bind
completely and instead, use arrow functions. Since arrow functions don't bind their own this
, it means this
refers to the enclosing context. In the example above, a method is not used thus this
refers to the class (the enclosing context). That will correctly call the class method updateWord
, and consequently, you need not change the onInput
event handler if you go this route.