React form onChange->setState one step behind

JavascriptReactjs

Javascript Problem Overview


I encountered this problem building a webapp and I replicated it in this jsfiddle. Essentially, I would like an input to call this.setState({message: input_val}) every time I type something into it, then pass it into the parent App class which then re-renders the message onto the Message class. However the output seems to always be one step behind what I actually type. The jsfiddle demo should be self explanatory. I am wondering if I did anything wrong to prompt this.

html

<script src="http://facebook.github.io/react/js/jsfiddle-integration.js"></script>
<div id='app'></div>

js

var App = React.createClass({
    getInitialState: function() {
        return {message: ''}
    },
    appHandleSubmit: function(state) {
        this.setState({message: state.message});
        console.log(this.state.message);
    },
    render: function() {
        return (
            <div className='myApp'>
            <MyForm onChange={this.appHandleSubmit}/>
            <Message message={this.state.message}/>
            </div>
        );
    }
});

var MyForm = React.createClass({
    handleSubmit: function() {
        this.props.onChange(this.state);
    },
    handleChange: function(e) {
        console.log(e.target.value);
        this.setState({message: e.target.value});
        this.handleSubmit();
    },
    render: function() {
        return (
            <form className="reactForm" onChange={this.handleChange}>
            <input type='text' />
            </form>
        );
    }
});

var Message = React.createClass({
    render: function() {
        return (
            <div className="message">
                <p>{this.props.message}</p>
            </div>
        )
    }
});
    
React.render(
    <App />,
    document.getElementById('app')
);

Javascript Solutions


Solution 1 - Javascript

A call to setState isn't synchronous. It creates a "pending state transition." (See here for more details). You should explicitly pass the new input value as part of the event being raised (like to handleSubmit in your example).

See this example.

handleSubmit: function(txt) {
    this.props.onChange(txt);
},
handleChange: function(e) {
    var value = e.target.value;
    this.setState({message: value});
    this.handleSubmit(value);
},

Solution 2 - Javascript

There is a much simpler way to do this, setState(updater, callback) is an async function and it takes the callback as second argument,

Simply pass the handleSubmit as a callback to setState method, this way after setState is complete only handleSubmit will get executed.

For eg.

handleChange: function(e) {
    console.log(e.target.value);
    this.setState({message: e.target.value}, this.handleSubmit);
}

Try to change the handleChange() method like above and it will work.

for syntax of using setState check this link

Solution 3 - Javascript

I was pulling my hair out for like an hour because of this so I decided to share... If your callback is still one step behind and seemingly not working, ensure you don't CALL the function with parenthesis... Just pass it in. Rookie mistake.

RIGHT:

handleChange: function(e) {
    console.log(e.target.value);
    this.setState({message: e.target.value}, this.handleSubmit);
}

VS

WRONG:

handleChange: function(e) {
    console.log(e.target.value);
    this.setState({message: e.target.value}, this.handleSubmit());
}

Solution 4 - Javascript

with setState hook

useEffect(() => {
    your code...
}, [yourState]);

Solution 5 - Javascript

There's no reason for MyForm to be using state here. Also putting the onChange on the form instead of the input you're interested in is odd. Controlled components should be preferred because their behavior is more obvious, and any time App's message state changes (even if you e.g. allow Message to change it later), it'll be correct everywhere.

This also makes your code a bit shorter, and considerably simpler.

var App = React.createClass({
    getInitialState: function() {
        return {message: ''}
    },
    appHandleSubmit: function(message) {
        this.setState({message: message});
    },
    render: function() {
        return (
            <div className='myApp'>
                <MyForm onChange={this.appHandleSubmit} 
                        message={this.state.message} />
                <Message message={this.state.message}/>
            </div>
        );
    }
});

var MyForm = React.createClass({
    handleInputChange: function(e){
        this.props.onChange(e.target.value);
    },
    // now always in sync with the parent's state
    render: function() {
        return (
            <form className="reactForm">
                <input type='text' onChange={this.handleInputChange}
                       value={this.props.message} />
            </form>
        );
    }
});

jsbin

Solution 6 - Javascript

Knowing the problem is with not having asyncronous behaviour of setState I solved my issue with async await

onChangeEmail=async x =>{
await this.setState({email:x})
}

Solution 7 - Javascript

I found it very cumbersome for me to define 3 handler functions just to get some value to a component's state, so I decided not to use state at all. I just defined an additional property to the component that stored desired value.

So I ended up with a code that looked something like this:

//...
},
text: '',
handleChange: function(event) {
  this.text = event.target.value;
  this.forceUpdate();
},
render: function() {
	return <div>
      <InputComponent onChange={this.handleChange}/>
      <DisplayComponent textToDisplay={this.text}/>
      </div>
}
//...

Solution 8 - Javascript

You could refactor your class-based component to a functional component as someone else mentioned. The drawbacks are that this can be quite time-consuming depending on how many code lines you have to refactor and is likely prone to error.

I will use your example of a changeHandler to display how it could be done in a functional component.

const INITIAL_DATA = {
  message: ""
}
    
const [form, setForm] = useState({...INITIAL_DATA})

const changeHandler = (e) = setForm({
  ...form,
  [e.target.name]: e.target.value
})

<InputField name={message} value={form.message} onChange={changeHandler}>

^ The above code will produce that behavior as you explained of onChange being one step behind the setState. As others have said, this is due to the asynchronous nature of the setState hook.

The way to solve this is to use the useEffect hook, which allows you to introduce state dependencies. So when setState is finished going through its update cycle the useEffect will fire. So add the below to the above code and BAM.. it should show the state changes without a step delay.

useEffect(() => {
  doSomeValidationHere()
  orSendOffTheForm()
}, [form])

*Notice how we add a dependency array after the useEffect(() => {}) hook.

Extra info:

  • If the dependency array is empty it will only run on component mount and first render
  • If there is no array, it will run every time the page renders
  • If there is a state name in the array it will run every time that state is finished setting

Solution 9 - Javascript

or as in my case - just use onKeyUp, instead of down...

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKeith YongView Question on Stackoverflow
Solution 1 - JavascriptWiredPrairieView Answer on Stackoverflow
Solution 2 - Javascriptrc-chandanView Answer on Stackoverflow
Solution 3 - JavascriptTaylor A. LeachView Answer on Stackoverflow
Solution 4 - JavascriptAhmed BoutaraaView Answer on Stackoverflow
Solution 5 - JavascriptBrigandView Answer on Stackoverflow
Solution 6 - Javascriptishab acharyaView Answer on Stackoverflow
Solution 7 - JavascriptRuslan ProkopenkoView Answer on Stackoverflow
Solution 8 - JavascriptajsauleView Answer on Stackoverflow
Solution 9 - Javascriptmartis martisView Answer on Stackoverflow