Native validation
Formula ships with native typesafe validation, using the validate option of useForm. The shape of the validation
mirrors the shape of the form data.
A validator is a function which accepts the field value and returns a string or array of strings representing any
issues.
const form = useForm({ initialValues: { username: "", password: "" }, submit: values => login(values.username, values.password), validate: { username(username) { if (!username.length) return "Required"; } }})All validators are optional. In the above example, the password field is not validated on the client.
Objects
Section titled “Objects”Objects can be validated just like primitives by providing a validator function. If you do so, the errors will be associated with the object field and not with any sub-fields.
const form = useForm({ initialValues: { user: { username: "", email: "" } }, validate: { user(user) { if (!user.username.length || !user.email.length) return "Please fill in your details"; } }})Often you’ll want to validate the subfields of an object individually. This will associate the errors with those sub-fields. You can do so by providing an object which has the same properties as the object being validated.
const form = useForm({ initialValues: { user: { username: "", email: "" } }, validate: { user(user) { if (!user.username.length || !user.email.length) return "Please fill in your details"; user: { username(username) { if (!username.length) return "Required"; }, email(email) { if (!email.length) return "Required"; } } }})In some cases, it may make sense to both validate the object as a whole (perhaps to enforce invariants between
properties), and the individual fields of the object. You can achieve that by using the special _self property.
const form = useForm({ initialValues: { user: { username: "", email: "" } }, validate: { user: { _self({ username, email }) { if (username === email) return "Username cannot use your email"; }, username(username) { if (!username.length) return "Required"; }, email(email) { if (!email.length) return "Required"; } } }})Arrays
Section titled “Arrays”Array fields are validated similarly to object fields. You can provide a function to validate the whole array at once,
or an object to validate the individual elements, using the special property _each.
Like with object fields, if you want to validate both the array as a whole and its individual elements, you
can use the special _self property.
const form = useForm({ initialValues: { tags: [{ name: "react" }, { name: "" }] }, validate: { tags: { _self(tags) { if (!tags.length) return "Requires at least 1 tag" }, _each: { name(name) { if (!name.length) return "Required"; } } } }});Patterns
Section titled “Patterns”If you thought that validation logic in the above examples was verbose then I’d agree with you. The same logic was repeated multiple times to enforce that a field cannot be blank.
Since validators are functions, you can define your own collection of validators for re-use across different forms. You can even use higher-order functions to define parameterized validators.
This pattern can result in some very declarative validation logic in your forms.
function requiredString(value: string) { if (!value || !value.length) return "Required";}
function maxLength(max: number): ValueValidator<string> { return (value: string) => { if (value.length > max) return `Max is ${max}`; }}
const form = useForm({ initialValues: { user: { username: "", email: "" } }, validate: { user: { username: maxLength(100), email: requiredString } }})