Form validation
You can validate forms in two places: the client and the server.
We recommend doing both for different reasons:
- Validate fields on the client for a better user experience, field errors are displayed immediately
- Validate on the server for security, to prevent malicious users from submitting invalid data
Client-side validation
All form input components support client-side validation via the validate prop.
<Form>
<TextField
id="username"
name="username"
label="Username"
validate={(value) => {
if (!value) {
return 'Please enter your username';
}
}}
/>
</Form>The validate function is called anytime the value changes. The error message
returned from validate will be displayed underneath the input when the form is submitted.
Field error summary
Individual input fields will display their own validation errors inline. However, this approach does not give a very good experience to screen reader users. For screen readers it's better to display a summary of field errors in a single place so that they can see a high-level summary of all the errors in one place.
This is achieved by rendering a FieldErrorSummary component at the top of the form.
<Form>
<FormErrors>
<FieldErrorSummary
fieldLabels={{ username: 'Username', password: 'Password' }}
title="There are errors in your submission"
subtitle="Please correct your input in these fields:"
/>
</FormErrors>
<TextField
id="username"
name="username"
label="Username"
isRequired
validate={(value) => {
if (!value) {
return 'Please enter your username';
}
}}
/>
<TextField
id="password"
name="password"
label="Password"
isRequired
validate={(value) => {
if (!value) {
return 'Please enter your password';
}
}}
/>
<Button type="submit">Log in</Button>
</Form>Server-side validation
There are two types of errors that can be returned from a form action:
- Server error - An error that occurred on the server, e.g. an API error occurred
- Field errors - Validation errors for individual fields, e.g. a required field is not filled in
Server errors
Server errors are returned from the form action when an error occurs on the server, e.g. an API error occurred.
import { FormAction } from '@/ui/components/forms/types';
export const loginAction: FormAction = async (currentState, formData) => {
const values = getFormValues<{ email: string; password: string }>(formData);
try {
await login(values.email, values.password);
} catch (error) {
return {
status: 'error',
formData,
serverError: 'Failed to login',
};
}
// ...
};And then displayed in the form component like this:
const form = useFormAction<{ email: string; password: string }>(loginAction);
return (
<Form action={form.action}>
<FormErrors>
<ServerError errorMessage={form.serverError} title="An error occurred" />
</FormErrors>
</Form>
);Field errors
It's better to do all field level validation on the client side, but if you do need to validate fields on the server, you can also return field level errors from the form action.
export const loginAction: FormAction = async (currentState, formData) => {
const values = getFormValues<{ email: string; password: string }>(formData);
if (!values.email) {
return {
status: 'error',
formData,
fieldErrors: { email: 'Please enter your email address' },
};
}
};You then need to pass these errors to the form's validationErrors prop:
<Form validationErrors={form.fieldErrors}>
<FormErrors>
<FieldErrorSummary fieldLabels={{ email: 'Email' }} />
</FormErrors>
</Form>