* chore: first pass * chore: onto something I think * chore: closer but no cigar * chore: infer validator * chore: infer zod * feat: add validation transformer logic * chore: fix typings for react adapter * chore: fix issue with `this` not being defined properly * chore: mostly update FieldInfo types from Vue * chore: work on fixing type inferencing * fix: make ValidatorType optional * chore: make TName restriction easier to grok * chore: fix React types * chore: fix Vue types * chore: fix typing issues * chore: fix various linting items * chore: fix ESlint and validation logic * chore: fix inferencing from formdata * chore: fix form inferencing * chore: fix React TS types to match form validator logic * chore: fix Vue types * chore: migrate zod validation to dedicated package * chore: add first integration test for zod adapter * chore: enable non-validator types to be passed to validator * feat: add yup 1.x adapter * chore: add functionality and tests for form-wide validators * chore: fix typings of async validation types * fix: async validation should now run as-expected more often * chore: add async tests for Yup validator * chore: rename packages to match naming schema better * chore: add Zod examples for React and Vue * chore: add React and Vue Yup support * chore: fix formatting * chore: fix CI types * chore: initial work to drastically improve docs * docs: improve docs for validation * docs: add adapter validation docs
6.4 KiB
id, title
| id | title |
|---|---|
| form-validation | Form and Field 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 (
<>
{/* ... */}
</>
);
}}
/>