0

I am fairly new to JS and I am having a bit trouble in understanding how to properly implement the callback passed to setState in React, for a controlled input. The following code is what I have so far:

class App extends React.Component {
    ...
        this.state = {
            properties: {
                width: '',
                height: ''
            }
         this.handleChange = this.handleChange.bind(this); //edit 1
    }

   handleChange(e){
     this.setState(() => ({ properties[e.target.name]: e.target.value })) //edit 2  
   }

   render(){
       return(
         <input
           type="text"
           value={this.state.properties.width}
           name="width"
           onChange={this.handleChange} />
        ...
       )
   }      
}

https://codepen.io/anon/pen/YYQgNv?editors=0010

jeremyTob
  • 131
  • 10
  • that wont fix the issue. – lluisrojass Jan 02 '18 at 22:25
  • Seems like you missed to `bind` a `handleChange` method in a constructor function `this.handleChange = this.handleChange.bind(this)` – The Reason Jan 02 '18 at 22:28
  • Indeed I forgot it but it does not solve the issue as @Iluisrojass stated already. – jeremyTob Jan 02 '18 at 22:37
  • @Li357, it is not duplicate of the question you linked. This question has to do with `react` `SyntheticEvent` and the fact that they are being reused and nullified after the event callback has been invoked, which causes problems with `setState` which is async. – margaretkru Jan 02 '18 at 22:58
  • @margaretkru That's true, I'll lift the duplicate status though the linked question explains part of the problem. – Andrew Li Jan 02 '18 at 23:13
  • yes, I was pretty sure myself that proper handling of `this` context in the event handler would fix the problem. – margaretkru Jan 02 '18 at 23:16
  • Is there a good reason not to take the values off of the event before calling setState? – aaronjkrause Jan 03 '18 at 01:11

1 Answers1

1

You need to change handleChange declaration:

class App extends React.Component {
    ...
   handleChange = (e) => {
     this.setState({ properties[e.target.name]: e.target.value })  
   }
    ...
}

When you write handleChange = (e) => {...} it will bind this pointer of the function to your component so that you will be able to access setState as pointed out by @Li357, it doesn't not bind at all, on the contrary it creates a property of the class that is an arrow function that doesn't bind this, capturing the this value of the surrounding scope, the class.


Update:

It has been pointed out that using arrow functions as class properties is an experimental feature, so it is safer to use this.handleChange = this.handleChange.bind(this) in constructor of the component. I got the example working with this code:

handleChange(event) {
    const target = event.target;
    this.setState((prevState) => ({
        properties: {...prevState.properties, ...{ [target.name]: target.value } }
    })
  );
}

I am not entirely sure why it behaves the way it does, I am guessing it has to do with the fact that setState is async and react wraps events in its own SyntheticEvent which will be reused and all properties will be nullified after the event callback has been invoked (see react docs). So if you store the original reference to target outside of setState it will get scoped and used inside setState.

Here is a working example on codesandbox.


Update 2:

According to react docs, one can't access react SyntheticEvent in an asynchronous way. One way of dealing with this would to be call event.persist() which will remove the wrapper, but this might not be a good idea since SyntheticEvent is a cross-browser wrapper around the browser’s native event which makes sure the events work identically across all browsers.

margaretkru
  • 2,751
  • 18
  • 20
  • 1
    i might be wrong, but i believe this functionality requires a transpiler plugin, as it is not standard in es2015. – lluisrojass Jan 02 '18 at 22:24
  • @lluisrojass, yes, you are right, it is not part of es6. I wasn't aware of that since I usually use `stage-2` babel preset which includes this functionality. thanks for pointing it out! – margaretkru Jan 02 '18 at 22:31
  • @margaretkru I have updated the pen but the binding doesn't seem to make it work. – jeremyTob Jan 02 '18 at 22:31
  • @Li357, could you please point out some source explaining how arrow functions work under the hood when they are used as class properties? I can't seem to find any well-written explanation of what happens with `this` pointer. – margaretkru Jan 02 '18 at 23:12
  • @margaretkru You can test it out with [the Babel REPL](https://babeljs.io/repl/#?babili=false&browsers=&build=&builtIns=false&code_lz=MYGwhgzhAEBiD29oG8BQ1oCMwCdoF5oAKASgID4VoB6a3HeAd2gDMBXAO2ABcBLeDtAAODIQFMc3AJ7QA5vDExeHbkm4ALMdGACI3HGx7wcAGmiNNOLQAMNvCNeHxl3GKugatoSBHQZofgC-fn7YOKRUtFaybOB4ALZiGvAAJn4YQaiBQA&debug=false&circleciRepo=&evaluate=true&lineWrap=false&presets=es2017%2Creact%2Cstage-2&prettier=false&targets=&version=6.26.0). Arrow functions don't bind `this`. – Andrew Li Jan 02 '18 at 23:20