React Validator Hook
A React Hook to handle form validation messages.
Note: This is using the new React Hooks API Proposal
which is subject to change until React 16.7 final.
You'll need to install react
, react-dom
, etc at ^16.7.0-alpha.0
Features:
- multiple rules per field
- asynchronous rules with built-in concurrency prevention
- BYO rules and display logic
Install
npm i react-use-validator
Usage
See validator-creator for
background information about validator rules.
import React, { useState } from "react";
import useValidator, { createRule } from "react-use-validator";
const initialFormData = {
field1: "",
field2: ""
};
const filled = createRule("filled", value => value.length > 0, "Required");
function ExampleForm() {
const [formData, setFormData] = useState(initialFormData);
const [messages, validate] = useValidator({
field1: [filled],
field2: [filled]
});
function handleChange({ target: { name, value } }) {
const change = { [name]: value };
setFormData({ ...formData, ...change });
validate(change);
}
async function handleSubmit() {
const messages = await validate(formData);
if (
Object.entries(messages).filter(([prop, result]) => result !== null)
.length > 0
) {
return;
}
await API.submit(formData);
}
return (
<div>
<div>
<label>Field 1</label>
<input name="field1" value={formData.field1} onChange={handleChange} />
// The message output will display "Required" from the `filled` rule if
// the input value is empty
<span>{messages.field1}</span>
</div>
<div>
<label>Field 2</label>
<input name="field2" value={formData.field2} onChange={handleChange} />
<span>{messages.field2}</span>
</div>
<button onClick={handleSubmit}>Submit</button>
</div>
);
}
Asynchronous rules
The validator handles asynchronous validation rules (such as server-side
validation), but some care is required.
An asynchronous rule can be created using the createAsyncRule
function.
import { createAsyncRule } from "react-use-validator";
import API from "./api";
const server = createAsyncRule(
change => API.validate(change),
({ type, prop }) => `Error from server`
);
The rule can be added to a rule collection in the same way as for synchronous
rules.
const rules = {
field1: [filled, server],
field2: [filled, server]
};
- Rules are executed in the order they appear in the rule collection, stopping
at the first one that returns a non-null response.
- Asynchronous rules will only be called once per validation.
The validate()
function returned by useValidator
will prevent concurrent
calls to the same rule by throwing a ValidatorError exception if a call is
interrupted. This exception should be caught (and usually ignored) by the
Component
function ignoreValidatorError(error) {
if (!(error instanceof ValidatorError)) {
throw error;
}
}
async function handleChange({ target: { name, value } }) {
const change = { [name]: value };
setFormData({ ...formData, ...change });
await validate(change).catch(ignoreValidatorError);
}
If a "submit" event occurs while an asynchronous validation is still in progress
the event handler can block until the validation completes by calling
validate()
with no arguments. Again, ValidatorError
exceptions should be
caught and used to prevent further execution of the handler.
async function handleSubmit() {
try {
const messages = await validate();
} catch (error) {
return ignoreValidatorError(error);
}
}
Even after all validation rules have passed, submitting form data to the server
may still fail. If the server response contains a list of errors with
validation results ({ type, prop }
objects) the validator can be re-run
providing those errors so that another network request isn't needed.
async function handleSubmit() {
const response = await API.submit(formData)
if (response.errors) {
await validate(null, [[ server, response.errors ]])
} else {
}
}