Search code examples
javascriptreactjses6-classcreate-react-app

ES6 method is not defined in create-react-app


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>
      );
  }
}

Solution

  • 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 its this 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, or new.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.