Please note this blog post drafted long back and might be outdated. please notify me if you see any issues.
When I get question like which validation package you use in ReactJS, I usually answer saying I don’t use any. I go with HTML5 validation which native to browsers for simple validation and rest of them do it on server side.
In my case it served well enough for all my projects.May be your usecase may vary.
Why HTML5 validation
Here are the couple of reason why I choose to go with HTML5 validations
- Simple and Easy to implement
- No extra dependency
- Easy to maintain
- No need to learn another package and its api.
Basic implementation
For the basic version, we can use form
element and bind action to onSubmit
event.
This is will show validation error in browsers native way.
import React, { Component } from "react";
export default class Signup extends Component {
handleSubmit = evt => {
evt.preventDefault();
// implement the submit via xhr
};
render() {
return (
<section>
<div className="container">
<form onSubmit={this.handleSubmit}>
// other fields to validate
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Submit</button>
</div>
</div>
</form>
</div>
</section>
);
}
}
Since we are using submit
event, the handleSubmit
will get called only when there is no validation error.
Form submit programatically
Lets consider another situation where we have 2 submit buttons, where one will submit the form like before another one will submit only after some state change. So for second button we have to bind onClick
event and do submit programatically.
There is a catch in this scenario, when we do the submit programatically the native HTML5 validation won’t get triggered.
So we have to check the validation and report it ourselves. We can use checkValidity
and reportValidity
methods for this.
import React, { Component } from "react";
export default class Signup extends Component {
handleSubmit = evt => {
evt.preventDefault();
console.log("submit");
// implement the submit via xhr
}
handleSignup = evt => {
if (this.form.checkValidity()) {
this.form.submit();
} else {
this.form.reportValidity();
}
};
render() {
return (
<section>
<div className="container">
<form ref={form => (this.form = form)} onSubmit={this.handleSubmit}>
<div class="field">
<label class="label">Username</label>
<div class="control has-icons-left has-icons-right">
<input
class="input"
type="text"
placeholder="Name"
required
name="username"
/>
<span class="icon is-small is-left">
<i class="fas fa-user" />
</span>
</div>
</div>
<div class="field">
<label class="label">Email</label>
<div class="control has-icons-left has-icons-right">
<input
class="input"
type="email"
placeholder="Email input"
required
name="email"
/>
<span class="icon is-small is-left">
<i class="fas fa-envelope" />
</span>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Submit</button>
</div>
<div class="control">
<button
type="button"
onClick={this.handleSignup}
class="button is-warning"
>
Signup
</button>
</div>
</div>
</form>
</div>
</section>
);
}
}
The checkValidity
will return true if there is no validation error and reportValidity will trigger the invalid
event on
each invalid child inputs which result in showing errors to user.
Custom design for Error messages
In the above implementations the issue is error messages will be shown in the native way as per the browser implementation and no as per our custom design.
If we want the custom design to implement, we have to keep get the error messages for each field and keep those in state
. We can bind callback to invalid event, to collect error message from all the child inputs. We will use validationMessage
property on the input element to get the localised message for the validation failure.
import React, { Component } from "react";
export default class Signup extends Component {
state = {
fields: {},
fieldErrors: {}
};
handleSubmit = evt => {
evt.preventDefault();
// implement the submit via xhr
};
handleChange = evt => {
console.log("change :: ", evt.target.name);
const fieldErrors = {
...this.state.fieldErrors,
[evt.target.name]: ""
};
this.setState({ fieldErrors });
};
handleInvalid = evt => {
evt.preventDefault();
console.log(evt.target.name);
const fieldErrors = {
...this.state.fieldErrors,
[evt.target.name]: evt.target.validationMessage
};
this.setState({ fieldErrors });
};
render() {
const { fieldErrors } = this.state;
return (
<section>
<div className="container">
<form
onSubmit={this.handleSubmit}
onChange={this.handleChange}
onInvalid={this.handleInvalid}
>
<div class="field">
<label class="label">Username</label>
<div class="control has-icons-left has-icons-right">
<input
class={`input ${fieldErrors.username ? "is-danger" : ""}`}
type="text"
placeholder="Name"
required
name="username"
/>
<span class="icon is-small is-left">
<i class="fas fa-user" />
</span>
{fieldErrors.username && (
<span class="icon is-small is-right">
<i class="fas fa-exclamation-triangle" />
</span>
)}
</div>
{fieldErrors.username && (
<p class="help is-danger">{fieldErrors.username}</p>
)}
</div>
<div class="field">
<label class="label">Email</label>
<div class="control has-icons-left has-icons-right">
<input
class={`input ${fieldErrors.email ? "is-danger" : ""}`}
type="email"
placeholder="Email input"
required
name="email"
/>
<span class="icon is-small is-left">
<i class="fas fa-envelope" />
</span>
{fieldErrors.email && (
<span class="icon is-small is-right">
<i class="fas fa-exclamation-triangle" />
</span>
)}
</div>
{fieldErrors.email && (
<p class="help is-danger">{fieldErrors.email}</p>
)}
</div>
<div class="field">
<label class="label">Message</label>
<div class="control">
<textarea
class={`textarea ${fieldErrors.message ? "is-danger" : ""}`}
placeholder="Textarea"
required
name="message"
/>
</div>
{fieldErrors.message && (
<p class="help is-danger">{fieldErrors.message}</p>
)}
</div>
<div class="field">
<div class="control">
<label class="checkbox">
<input type="checkbox" required name="toc" />
I agree to the <a href="#">terms and conditions</a>
</label>
{fieldErrors.toc && (
<p class="help is-danger">{fieldErrors.toc}</p>
)}
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Submit</button>
</div>
</div>
</form>
</div>
</section>
);
}
}
Even though we are using onInvalid
on form, it will trigger handleInvalid
for each invalid field, and the handleChange
is using to clear the state once some update is done on the field. If you want to set csutom validation message you can read from my other post, HTML5 : custom validation message