React onChange always once 'step' behind


#1
getInitialState: function() {
    return {
      name: "",
      description: "",
      formValid: false,
      errors: {}
    };
  },

  handleChange: function(e) {
    this.setState({
      "name": ReactDOM.findDOMNode(this.refs.name).value,
      "description": ReactDOM.findDOMNode(this.refs.description).value
    });

    var formValid = true;
    if (this.state.name == "") { formValid = false; }
    if (this.state.description == "") { formValid = false; }
    this.setState({ "formValid": formValid });
  },

And in my render():

            <div className="form-group">
              <label for="communityName" className="col-sm-3 control-label">Community Name</label>
              <div className="col-sm-9">
                <input ref="name" onChange={this.handleChange} type="text" className="form-control" id="communityName" />
              </div>
            </div>
            <div className="form-group">
              <label for="communityAbout" className="col-sm-3 control-label">About</label>
              <div className="col-sm-9">
                <textarea ref="description" onChange={this.handleChange} className="form-control" id="communityAbout" rows="3"></textarea>
              </div>
            </div>

           <button disabled={!this.state.formValid} type="submit" className="btn btn-sm btn-primary">Create</button>

When I type in values in either input or clear out the inputs, the button changes it’s state one more ‘change’ event than it should.

What am I doing wrong? Why is the component always ‘one step behind’ what it should be?

I’m following this simple example and I don’t see why the example works as expected, and this doesn’t. http://jsbin.com/fitiha/8/edit?js,output


#2

Ok, this is strange but it works.

<button disabled={!this.formValid()} type="submit" className="btn btn-sm btn-primary">Create</button>

formValid: function () {
    var formValid = true;
    if (this.state.name == "") { formValid = false; }
    if (this.state.description == "") { formValid = false; }
    return formValid;
  },

I don’t know why moving it to a separate function makes it work the right way and update the button in real time.


#3

The problem is that when you calculate formValid in handleChange, the state values it depends on are not really set yet, as you can check in the API docs:

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

As Thinking in React says, the state should ideally contain only the minimal set of values your app needs. Values that can be computed based on other state or prop values (which is the case for formValid) shouldn’t be considered state. You could also simply calculate formValid inside the render method itself, before you call return:

render: function () {
  let formValid = ...

  return (
    ...
    <button disabled={!formValid}...

#4

Ok so I think I know what it’s working with the function being separated.

Basically:

State Changes -> Render is called sometime -> Template uses the function -> Function returns true or false for disabled`.

Not really intuitive but I guess I can get used to it.


#5

I just came across this problem. A solution to get the new value is to use a callback as shown in https://stackoverflow.com/questions/30782948/why-calling-react-setstate-method-doesnt-mutate-the-state-immediately:

this.setState({value: event.target.value}, () => {
    console.log(this.state.value);
});