0

I'm just starting to learn React.js (and Javascript) and I have a very basic question for you guys.

This is a working example of a small component that creates 3 buttons for which a value is incremented each time they are clicked.

class Button extends React.Component {

handleClick = () => {
    this.props.onClickFunction(this.props.incrementValue);
}
render(){
    return(
    <button onClick={this.handleClick}>
        {this.props.incrementValue}
    </button>
    );
    }
}

const Result = (props) => {
    return(
    <div>{props.counter}</div>
  );
};

class App extends React.Component {
    state = {counter: 0};
  incrementCounter = (incrementValue) => {
    this.setState((prevState) => ({
            counter: prevState.counter + incrementValue
    }));
    };

    render() {
    return (
        <div>
        <Button incrementValue={2} onClickFunction={this.incrementCounter}/>
      <Button incrementValue={10} onClickFunction={this.incrementCounter}/>
      <Button incrementValue={99} onClickFunction={this.incrementCounter}/>
      <Result counter={this.state.counter}/>
    </div>
    );  
  }
}

ReactDOM.render(<App />, mountNode);

While experiencing with the code, I tried to change the handleClick function.

class Button extends React.Component {

handleClick(){
    this.props.onClickFunction(this.props.incrementValue);
}
render(){
    return(
    <button onClick={this.handleClick}>
        {this.props.incrementValue}
    </button>
    );
    }
}

const Result = (props) => {
    return(
    <div>{props.counter}</div>
  );
};

class App extends React.Component {
    state = {counter: 0};
  incrementCounter = (incrementValue) => {
    this.setState((prevState) => ({
            counter: prevState.counter + incrementValue
    }));
    };

    render() {
    return (
        <div>
        <Button incrementValue={2} onClickFunction={this.incrementCounter}/>
      <Button incrementValue={10} onClickFunction={this.incrementCounter}/>
      <Button incrementValue={99} onClickFunction={this.incrementCounter}/>
      <Result counter={this.state.counter}/>
    </div>
    );  
  }
}

ReactDOM.render(<App />, mountNode);

I am now getting: Uncaught TypeError: Cannot read property 'props' of undefined at handleClick (eval at

From my understanding, the anonymous function handleClick = () =>... can access the props from the parent because of a closure but why does the magic stop when I replace it with a class method?

Max
  • 2,529
  • 1
  • 18
  • 29
  • 1
    `handleClick` isn't an anonymous function. it's a named function, named `handleClick`. – Mulan Dec 05 '18 at 20:19
  • 1
    This has nothing to do with closures but with how `this` works. Have a look at [How does the “this” keyword work?](https://stackoverflow.com/q/3127429/218196), [How to access the correct `this` inside a callback?](https://stackoverflow.com/q/20279484/218196) and [Unable to access React instance (this) inside event handler](https://stackoverflow.com/q/29577977/218196) . `handleClick = () => ...` is a *class property* with an *arrow function*. These two parts make it so that `this` inside the function refers to the component instance. – Felix Kling Dec 05 '18 at 20:22
  • 1
    The use of correct terms will allow you to resolve issues faster. It's not just a [closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) but an [arrow](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions). The difference is not that one is anonymous and another is not. One is arrow function and *instance* method, another is regular function and *prototype* method. – Estus Flask Dec 05 '18 at 20:23
  • Thank you so much guys. I should really learn JS before React. LOL! – Max Dec 05 '18 at 20:25
  • 1
    In general yes :) However, class properties are an experimental feature that is primarily used with React. – Felix Kling Dec 05 '18 at 20:25
  • 2
    Consider starting with how ES6 classes work. The thing you're using is https://github.com/tc39/proposal-class-fields , it's not even standardized. – Estus Flask Dec 05 '18 at 20:26

1 Answers1

1

Your issue does not seem to be about closures, but rather about how this works in JS. In your working example

handleClick = () => {
    this.props.onClickFunction(this.props.incrementValue);
}

You have an arrow function, and as a result, your this always points to your instance, which is why you can access this.props.

In your non working example

handleClick(){
    this.props.onClickFunction(this.props.incrementValue);
}

You no longer have an arrow function, so now the this no longer refers to your instance when the function is called. This is why you cannot access this.props.

You can fix this by using an arrow function like you have in the working case, or you can bind your function to this current instance in order to make sure this always points to your instance.

Chaim Friedman
  • 6,124
  • 4
  • 33
  • 61