0

In my render() I have the following component with event handling.

render() { ...
  <input type="text" 
         onChange={(_) => { this.restate("email", _.target.value); }} />
}

private restate(type: string, input: any) { ...
  if (type === "email")
    this.setState({ ... });
}

This works and behaves as supposed to. Since I'm learning React right now, I try to improve my syntax so it's smooth on the eyes. I've found this answer and liked the brevity of the event handler assignment. So I've changed my rendering and implemented the handler to be passed as follows.

render() { ...
  <input type="text" onChange={ this.handleEmail } />
}

private handleEmail(data: React.ChangeEvent<HTMLInputElement>) {
  console.log("hit before: " + data.target.value);
  console.log(this);
  this.restate("email", data.target.value);
}

The change in the markup seems to work, because the method handleEmail(...) is invoked. However, I get an error saying that restate(...) is not a method of undefined and, in fact, when I investigate what this is, I discovered it's undefined.

I need help understanding what I'm doing wrong and how it deviates from the article linked to above. I tried to google for the syntax differences between versions of React but got nothing. And I'm not sure how to describe the issue in correct technical terms.

DonkeyBanana
  • 3,266
  • 4
  • 26
  • 65
  • Did you bind your function in `constructor()` ? – kiranvj Sep 23 '18 at 14:55
  • [binding patterns](https://medium.freecodecamp.org/react-binding-patterns-5-approaches-for-handling-this-92c651b5af56) – xadm Sep 23 '18 at 15:05
  • Instead of binding it you can this.handleEmail(e) } /> – Lazar Nikolic Sep 23 '18 at 15:15
  • @kiranvj No, I did not. Now, that you say so, I see that part implemented in the constructor of the provided example. It seems to work so you might want to repost your comment as a reply so it can be accepted. Still, the syntax *this.foo=this.foo.bind(this)* looks retarded. If *this* is there, then it should be there without putting it there. I guess it's my ignorance of React that shines through here... – DonkeyBanana Sep 23 '18 at 15:47
  • @LazarNikolic Would you mind reposting your comment as a reply and fill in a row or two discussing pros and cons of the different bind/-not-bind approaches? It might be useful for the future generations (starting with me). – DonkeyBanana Sep 23 '18 at 15:49
  • I wrote it as an answer. Hope it is clear :) – Lazar Nikolic Sep 24 '18 at 09:15

2 Answers2

1

What I think you should do is

render() { ...
  <input type="text" onChange={(e) => this.handleEmail(e) } />
}

I think the reason why your restate method was not a method of undefined is because your method was not binded to the component. In order for your methods to be a part of react class you need to bind that method to the class. There are couple of ways to do that. In class constructor like this:

 constructor(props) {
  super(props);
  this.handleEmail = this.handleEmail.bind(this);
 }

or inside render function, like I showed above. Third is to use arrow function inside class body like this:

handleEmail = () => {
  // call this function from render 
  // and this.whatever in here works fine.
};
Lazar Nikolic
  • 4,261
  • 1
  • 22
  • 46
1

In order to use this inside your onClick function you need to bind it inside the constructor. You can also use bind in render() also, but this is not recommended as bind will be called multiple times. The react docs recommends to use bind inside constructor like this

constructor() {
  super(); 
  this.handleEmail = this.handleEmail.bind(this);
}

This is not something specific to React, its JavaScript. Its by design.

This is what bind does

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

Question

Why does you first example worked?

render() { ...
  <input type="text" 
         onChange={(_) => { this.restate("email", _.target.value); }} />
}

This is because you used the ES6 arrow function ()=> {}. This is also called fat arrow functions.

Whenever you use arrow function, this is captured from the surrounded context and you can use this inside your function without binding. That why your first example worked.

Your second example would have worked if you have used like this

render() { ...
  <input type="text" onChange={ ()=> this.handleEmail() } />
}

I would suggest to read this excellent article for more details on bind

kiranvj
  • 32,342
  • 7
  • 71
  • 76