6.4 KiB
id, title
| id | title |
|---|---|
| form-validation | Form and Field Validation |
At the core of TanStack Form's functionalities is the concept of validation. We currently support three mechanisms of validation:
- Synchronous functional validation
- Asynchronous functional validation
- Adapter-based validation
Let's take a look at each and see how they're built.
Synchronous Functional Validation
With Form, you can pass a function to a field and, if it returns a string, said string will be used as the error:
<form.Field
name="age"
onChange={val => val < 13 ? "You must be 13 to make an account" : undefined}
children={(field) => {
return (
<>
<label htmlFor={field.name}>First Name:</label>
<input
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
type="number"
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{field.state.meta.touchedErrors ? (
<em>{field.state.meta.touchedErrors}</em>
) : null}
</>
);
}}
/>
Displaying Errors
Once you have your validation in place, you can map the errors from an array to be displayed in your UI:
<form.Field
name="age"
onChange={val => val < 13 ? "You must be 13 to make an account" : undefined}
children={(field) => {
return (
<>
{/* ... */}
{field.state.meta.errors ? (
<em>{field.state.meta.errors}</em>
) : null}
</>
);
}}
/>
Or use the errorMap property to access the specific error you're looking for:
<form.Field
name="age"
onChange={val => val < 13 ? "You must be 13 to make an account" : undefined}
children={(field) => {
return (
<>
{/* ... */}
{field.state.meta.errorMap['onChange'] ? (
<em>{field.state.meta.errorMap['onChange']}</em>
) : null}
</>
);
}}
/>
Using Alternative Validation Steps
One of the great benefits of using TanStack Form is that you're not locked into a specific method of validation. For example, if you want to validate a specific field on blur rather than on text change, you can change onChange to onBlur:
<form.Field
name="age"
onBlur={val => val < 13 ? "You must be 13 to make an account" : undefined}
children={(field) => {
return (
<>
{/* ... */}
</>
);
}}
/>
Asynchronous Functional Validation
While we suspect most validations will be synchronous, there's many instances where a network call or some other async operation would be useful to validate against.
To do this, we have dedicated onChangeAsync, onBlurAsync, and other methods that can be used to validate against:
<form.Field
name="firstName"
onChangeAsync={async (value) => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return (
value.includes("error") && 'No "error" allowed in first name'
);
}}
children={(field) => {
return (
<>
<label htmlFor={field.name}>First Name:</label>
<input
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<FieldInfo field={field} />
</>
);
}}
/>
This can be combined with the respective synchronous properties as well:
<form.Field
name="firstName"
onChange={(value) =>
!value
? "A first name is required"
: value.length < 3
? "First name must be at least 3 characters"
: undefined
}
onChangeAsync={async (value) => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return (
value.includes("error") && 'No "error" allowed in first name'
);
}}
children={(field) => {
return (
<>
{/* ... */}
</>
);
}}
/>
Built-in Debouncing
While async calls are the way to go when validating against the database, running a network request on every keystroke is a good way to DDOS your database.
Instead, we enable an easy method for debouncing your async calls by adding a single property:
<form.Field
name="firstName"
asyncDebounceMs={500}
onChangeAsync={async (value) => {
// ...
}}
children={(field) => {
return (
<>
{/* ... */}
</>
);
}}
/>
This will debounce every async call with a 500ms delay. You can even override this property on a per-validation property:
<form.Field
name="firstName"
asyncDebounceMs={500}
onChangeAsyncDebounceMs={1500}
onChangeAsync={async (value) => {
// ...
}}
onBlurAsync={async (value) => {
// ...
}}
children={(field) => {
return (
<>
{/* ... */}
</>
);
}}
/>
This will run
onChangeAsyncevery 1500ms whileonBlurAsyncwill run every 500ms.
Adapter-Based Validation
While functions provide more flexibility and customization over your validation, they can be a bit verbose. To help solve this, there are libraries like Yup and Zod that provide schema-based validation to make shorthand and type-strict validation substantially easier.
Luckily, we support both of these libraries through official adapters:
$ npm install @tanstack/zod-form-adapter zod
# or
$ npm install @tanstack/yup-form-adapter yup
Once done, we can add the adapter to the validator property on the form or field:
import { zodValidator } from "@tanstack/zod-form-adapter";
import { z } from "zod";
// ...
const form = useForm({
// Either add the validator here or on `Field`
validator: zodValidator,
// ...
});
<form.Field
name="firstName"
validator={zodValidator}
onChange={z
.string()
.min(3, "First name must be at least 3 characters")}
children={(field) => {
return (
<>
{/* ... */}
</>
);
}}
/>
These adapters also support async operations using the proper property names:
<form.Field
name="firstName"
onChange={z
.string()
.min(3, "First name must be at least 3 characters")}
onChangeAsyncDebounceMs={500}
onChangeAsync={z.string().refine(
async (value) => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return !value.includes("error");
},
{
message: "No 'error' allowed in first name",
},
)}
children={(field) => {
return (
<>
{/* ... */}
</>
);
}}
/>