Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React form onChange->setState one step behind

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')
);
like image 926
Keith Yong Avatar asked Feb 27 '15 20:02

Keith Yong


Video Answer


4 Answers

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);
},
like image 188
WiredPrairie Avatar answered Nov 01 '22 10:11

WiredPrairie


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

like image 33
rc-chandan Avatar answered Nov 01 '22 12:11

rc-chandan


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());
}
like image 22
Taylor A. Leach Avatar answered Nov 01 '22 12:11

Taylor A. Leach


with setState hook

useEffect(() => {
    your code...
}, [yourState]);
like image 24
Ahmed Boutaraa Avatar answered Nov 01 '22 12:11

Ahmed Boutaraa