Skip to content

Validation

Formulier supports field-level validation. This means you provide each form field that needs validation with an optional validator function. This makes it easy to validate when the form has conditional fields or requires dynamic validation rules depending on other fields for example.

Example: Min Length

Let's validate that the firstName and lastName are at least 2 characters long.
We start with the form we made on the Getting Started page.

jsx
import * as React from 'react'
import {FormProvider, useCreateForm, useFormField, useFormInstance, useSubmitHandler} from '@formulier/react'

export default function Example() {
	const form = useCreateForm({
		initialValues: {
			firstName: '',
			lastName: '',
			email: '',
		},
	})

	const onSubmit = useSubmitHandler(form, values => {
		alert(JSON.stringify(values, null, 2))
	})

	return (
		<form onSubmit={onSubmit}>
			<FormProvider form={form}>
				<InputField name="firstName" label="First name" minLength={2} />
				<InputField name="lastName" label="Last name" minLength={2} />
				<InputField name="email" label="Email" type="email" />

				<button type="submit">Submit</button>
			</FormProvider>
		</form>
	)
}

function InputField({name, label, type = 'text', minLength}) {
	const form = useFormInstance()

	const validate = React.useCallback(
		value => {
			if (minLength !== undefined) {
				if (typeof value === 'string' && value.length < minLength) {
					return `Value should be at least ${minLength} characters long!`
				}
			}
			return null
		},
		[minLength],
	)

	const [field, meta] = useFormField(form, {name, validate})

	return (
		<div className="field">
			<label className="label" htmlFor={name}>
				{label}
			</label>
			<input
				{...field}
				className="input"
				type={type}
				name={name}
				value={field.value || ''}
				onChange={event => field.onChange(event.target.value)}
			/>
			{meta.error && <span className="error">{meta.error}</span>}
		</div>
	)
}

Example: Required

Let's also make sure that all our fields are always filled out.
Continuing on where we left off:

jsx
import * as React from 'react'
import {FormProvider, useCreateForm, useFormField, useFormInstance, useSubmitHandler} from '@formulier/react'

export default function Example() {
	const form = useCreateForm({
		initialValues: {
			firstName: '',
			lastName: '',
			email: '',
		},
	})

	const onSubmit = useSubmitHandler(form, values => {
		alert(JSON.stringify(values, null, 2))
	})

	return (
		<form onSubmit={onSubmit}>
			<FormProvider form={form}>
				<InputField name="firstName" label="First name" minLength={2} required />
				<InputField name="lastName" label="Last name" minLength={2} required />
				<InputField name="email" label="Email" type="email" required />

				<button type="submit">Submit</button>
			</FormProvider>
		</form>
	)
}

function InputField({name, label, type = 'text', minLength, required}) {
	const form = useFormInstance()

	const validate = React.useCallback(
		value => {
			if (required) {
				if (value === '') {
					return 'Value is required!'
				}
			}
			if (minLength !== undefined) {
				if (typeof value === 'string' && value.length < minLength) {
					return `Value should be at least ${minLength} characters long!`
				}
			}
			return null
		},
		[required, minLength],
	)

	const [field, meta] = useFormField(form, {name, validate})

	return (
		<div className="field">
			<label className="label" htmlFor={name}>
				{label}
			</label>
			<input
				{...field}
				className="input"
				type={type}
				name={name}
				value={field.value || ''}
				onChange={event => field.onChange(event.target.value)}
			/>
			{meta.error && <span className="error">{meta.error}</span>}
		</div>
	)
}

Example: Results

Below you can see the result of our changes.

Validation Timing

validation functions are called:

  • When the user tries to submit the form
  • When the field loses focus (Meaning the user "touched" the field)
  • When the field is touched and the value changes (E.g. the user types a character in the input)

Example

Imagine the user focuses the firstName field and tabs to the lastName field. The firstName field will now show the "Value is required!" error. If the user then goes back to firstName and types one character, the validation function is run again and now the error will be "Value should be at least 2 characters long!". Adding 2 more characters will make the error disappear.

Validation Order

It's important to note that a field can only have one error at a time. Therefore whichever error string is returned by our validate function is the one that's available from meta.error.

In our example above it makes sense to validate required before minLength so if we leave the field empty, we will see the required error instead of the min length one.