I am trying to create client side validation with ReactJS on my registration form. I am using http://validatejs.org/ library for validations along with https://github.com/jhudson8/react-semantic-ui components for rendering semantic-ui React components. Here is the code.
var constraints = {
email: {
presence: true,
email:true
},
password: {
presence: true,
length: { minimum: 5 }
}
}
var RegistrationForm = React.createClass({
getInitialState: function() {
return {
data: {},
errors: {}
};
},
changeState: function () {
this.setState({
data: {
email: this.refs.email.getDOMNode().value,
password: this.refs.password.getDOMNode().value,
password_confirmation: this.refs.password_confirmation.getDOMNode().value
}
});
console.log("State after update");
console.log(this.state.data);
},
handleChange: function(e) {
this.changeState();
var validation_errors = validate(this.state.data, constraints);
if(validation_errors){
console.log(validation_errors);
this.setState({errors: validation_errors});
}
else
this.setState({errors: {}});
},
handleSubmit: function(e) {
e.preventDefault();
//code left out..
},
render: function() {
var Control = rsui.form.Control;
var Form = rsui.form.Form;
var Text = rsui.input.Text;
var Button = rsui.form.Button;
return (
<Form onSubmit={this.handleSubmit} onChange={this.handleChange}>
<h4 className="ui dividing header">New User Registration</h4>
<Control label="Email" error={this.state.errors.email}>
<Text name="email" type="email" ref="email" key="email" value={this.state.data.email}></Text>
</Control>
<Control label="Password" error={this.state.errors.password}>
<Text name="password" type="password" ref="password" key="password" value={this.state.data.password}></Text>
</Control>
<Control label="Password Confirmation">
<Text name="password_confirmation" type="password" ref="password_confirmation" key="password_confirmation" value={this.state.data.password_confirmation}></Text>
</Control>
<Button> Register </Button>
</Form>);
}
});
The problem I am having is that when I call this.setState, the state is not immediately updated, so when I call validate(this.state.data, constraints) I am validating previous state, so user's UI experience gets weird, for example:
If I have 'example@em' in my email field and I enter 'a', it will validate string 'example@em' not 'example@ema', so in essence it always validates the state before the new key stroke. I must be doing something fundamentally wrong here. I know state of the component is not updated right away, only after render is done.
Should I be doing validations in render function ?
--- SOLUTION ---
Adding a callback to setState like Felix Kling suggested solved it. Here is the updated code with solution:
var RegistrationForm = React.createClass({
getInitialState: function() {
return {
data: {},
errors: {}
};
},
changeState: function () {
this.setState({
data: {
email: this.refs.email.getDOMNode().value,
password: this.refs.password.getDOMNode().value,
password_confirmation: this.refs.password_confirmation.getDOMNode().value
}
},this.validate);
},
validate: function () {
console.log(this.state.data);
var validation_errors = validate(this.state.data, constraints);
if(validation_errors){
console.log(validation_errors);
this.setState({errors: validation_errors});
}
else
this.setState({errors: {}});
},
handleChange: function(e) {
console.log('handle change fired');
this.changeState();
},
handleSubmit: function(e) {
e.preventDefault();
console.log(this.state);
},
render: function() {
var Control = rsui.form.Control;
var Form = rsui.form.Form;
var Text = rsui.input.Text;
var Button = rsui.form.Button;
return (
<Form onSubmit={this.handleSubmit} onChange={this.handleChange}>
<h4 className="ui dividing header">New Rowing Club Registration</h4>
<Control label="Email" error={this.state.errors.email}>
<Text name="email" type="email" ref="email" key="email" value={this.state.data.email}></Text>
</Control>
<Control label="Password" error={this.state.errors.password}>
<Text name="password" type="password" ref="password" key="password" value={this.state.data.password}></Text>
</Control>
<Control label="Password Confirmation">
<Text name="password_confirmation" type="password" ref="password_confirmation" key="password_confirmation" value={this.state.data.password_confirmation}></Text>
</Control>
<Button> Register </Button>
</Form>);
}
});
--- BETTER SOLUTION -----
See FakeRainBrigand's solution below.