TanStack Form 功能的核心是验证的概念。TanStack Form 使验证高度可定制
这取决于您!<Field /> 组件接受一些回调作为 props,例如 onChange 或 onBlur。这些回调被传递字段的当前值以及 fieldAPI 对象,以便您可以执行验证。如果您发现验证错误,只需返回错误消息作为字符串,它将可在 field().state.meta.errors 中使用。
这是一个例子
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{(field) => (
<>
<label for={field().name}>Age:</label>
<input
id={field().name}
name={field().name}
value={field().state.value}
type="number"
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
/>
{field().state.meta.errors ? (
<em role="alert">{field().state.meta.errors.join(', ')}</em>
) : null}
</>
)}
</form.Field>
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{(field) => (
<>
<label for={field().name}>Age:</label>
<input
id={field().name}
name={field().name}
value={field().state.value}
type="number"
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
/>
{field().state.meta.errors ? (
<em role="alert">{field().state.meta.errors.join(', ')}</em>
) : null}
</>
)}
</form.Field>
在上面的例子中,验证是在每次击键时完成的 (onChange)。相反,如果我们希望在字段失焦时完成验证,我们将像这样更改上面的代码
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{(field) => (
<>
<label for={field().name}>Age:</label>
<input
id={field().name}
name={field().name}
value={field().state.value}
type="number"
// Listen to the onBlur event on the field
onBlur={field().handleBlur}
// We always need to implement onInput, so that TanStack Form receives the changes
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
/>
{field().state.meta.errors ? (
<em role="alert">{field().state.meta.errors.join(', ')}</em>
) : null}
</>
)}
</form.Field>
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{(field) => (
<>
<label for={field().name}>Age:</label>
<input
id={field().name}
name={field().name}
value={field().state.value}
type="number"
// Listen to the onBlur event on the field
onBlur={field().handleBlur}
// We always need to implement onInput, so that TanStack Form receives the changes
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
/>
{field().state.meta.errors ? (
<em role="alert">{field().state.meta.errors.join(', ')}</em>
) : null}
</>
)}
</form.Field>
因此,您可以通过实现所需的回调来控制何时完成验证。您甚至可以在不同时间对同一字段执行不同的验证
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
onBlur: ({ value }) => (value < 0 ? 'Invalid value' : undefined),
}}
>
{(field) => (
<>
<label for={field().name}>Age:</label>
<input
id={field().name}
name={field().name}
value={field().state.value}
type="number"
// Listen to the onBlur event on the field
onBlur={field().handleBlur}
// We always need to implement onInput, so that TanStack Form receives the changes
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
/>
{field().state.meta.errors ? (
<em role="alert">{field().state.meta.errors.join(', ')}</em>
) : null}
</>
)}
</form.Field>
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
onBlur: ({ value }) => (value < 0 ? 'Invalid value' : undefined),
}}
>
{(field) => (
<>
<label for={field().name}>Age:</label>
<input
id={field().name}
name={field().name}
value={field().state.value}
type="number"
// Listen to the onBlur event on the field
onBlur={field().handleBlur}
// We always need to implement onInput, so that TanStack Form receives the changes
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
/>
{field().state.meta.errors ? (
<em role="alert">{field().state.meta.errors.join(', ')}</em>
) : null}
</>
)}
</form.Field>
在上面的例子中,我们在不同时间(每次击键和字段失焦时)对同一字段验证不同的内容。由于 field().state.meta.errors 是一个数组,因此会显示给定时间的所有相关错误。您还可以使用 field().state.meta.errorMap 来根据验证完成的时间(onChange、onBlur 等...)获取错误。有关显示错误的更多信息,请参见下文。
一旦您设置好验证,您可以将错误从数组映射出来以显示在您的 UI 中
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{(field) => {
return (
<>
{/* ... */}
{field().state.meta.errors.length ? (
<em>{field().state.meta.errors.join(',')}</em>
) : null}
</>
)
}}
</form.Field>
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{(field) => {
return (
<>
{/* ... */}
{field().state.meta.errors.length ? (
<em>{field().state.meta.errors.join(',')}</em>
) : null}
</>
)
}}
</form.Field>
或者使用 errorMap 属性来访问您正在查找的特定错误
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{(field) => (
<>
{/* ... */}
{field().state.meta.errorMap['onChange'] ? (
<em>{field().state.meta.errorMap['onChange']}</em>
) : null}
</>
)}
</form.Field>
<form.Field
name="age"
validators={{
onChange: ({ value }) =>
value < 13 ? 'You must be 13 to make an account' : undefined,
}}
>
{(field) => (
<>
{/* ... */}
{field().state.meta.errorMap['onChange'] ? (
<em>{field().state.meta.errorMap['onChange']}</em>
) : null}
</>
)}
</form.Field>
值得一提的是,我们的 errors 数组和 errorMap 与验证器返回的类型匹配。这意味着
<form.Field
name="age"
validators={{
onChange: ({ value }) => (value < 13 ? { isOldEnough: false } : undefined),
}}
>
{(field) => (
<>
{/* ... */}
{/* errorMap.onChange is type `{isOldEnough: false} | undefined` */}
{/* meta.errors is type `Array<{isOldEnough: false} | undefined>` */}
{!field().state.meta.errorMap['onChange']?.isOldEnough ? (
<em>The user is not old enough</em>
) : null}
</>
)}
</form.Field>
<form.Field
name="age"
validators={{
onChange: ({ value }) => (value < 13 ? { isOldEnough: false } : undefined),
}}
>
{(field) => (
<>
{/* ... */}
{/* errorMap.onChange is type `{isOldEnough: false} | undefined` */}
{/* meta.errors is type `Array<{isOldEnough: false} | undefined>` */}
{!field().state.meta.errorMap['onChange']?.isOldEnough ? (
<em>The user is not old enough</em>
) : null}
</>
)}
</form.Field>
如上所示,每个 <Field> 通过 onChange、onBlur 等回调接受其自己的验证规则。也可以通过将类似的回调传递给 createForm() 钩子,在表单级别(而不是逐字段)定义验证规则。
例子
export default function App() {
const form = createForm(() => ({
defaultValues: {
age: 0,
},
onSubmit: async ({ value }) => {
console.log(value)
},
validators: {
// Add validators to the form the same way you would add them to a field
onChange({ value }) {
if (value.age < 13) {
return 'Must be 13 or older to sign'
}
return undefined
},
},
}))
// Subscribe to the form's error map so that updates to it will render
// alternately, you can use `form.Subscribe`
const formErrorMap = form.useStore((state) => state.errorMap)
return (
<div>
{/* ... */}
{formErrorMap().onChange ? (
<div>
<em>There was an error on the form: {formErrorMap().onChange}</em>
</div>
) : null}
{/* ... */}
</div>
)
}
export default function App() {
const form = createForm(() => ({
defaultValues: {
age: 0,
},
onSubmit: async ({ value }) => {
console.log(value)
},
validators: {
// Add validators to the form the same way you would add them to a field
onChange({ value }) {
if (value.age < 13) {
return 'Must be 13 or older to sign'
}
return undefined
},
},
}))
// Subscribe to the form's error map so that updates to it will render
// alternately, you can use `form.Subscribe`
const formErrorMap = form.useStore((state) => state.errorMap)
return (
<div>
{/* ... */}
{formErrorMap().onChange ? (
<div>
<em>There was an error on the form: {formErrorMap().onChange}</em>
</div>
) : null}
{/* ... */}
</div>
)
}
虽然我们认为大多数验证将是同步的,但在许多情况下,网络调用或某些其他异步操作对于针对验证将非常有用。
为此,我们有专门的 onChangeAsync、onBlurAsync 和其他方法,可用于针对验证
<form.Field
name="age"
validators={{
onChangeAsync: async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return value < 13 ? 'You must be 13 to make an account' : undefined
},
}}
>
{(field) => (
<>
<label for={field().name}>Age:</label>
<input
id={field().name}
name={field().name}
value={field().state.value}
type="number"
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
/>
{field().state.meta.errors ? (
<em role="alert">{field().state.meta.errors.join(', ')}</em>
) : null}
</>
)}
</form.Field>
<form.Field
name="age"
validators={{
onChangeAsync: async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return value < 13 ? 'You must be 13 to make an account' : undefined
},
}}
>
{(field) => (
<>
<label for={field().name}>Age:</label>
<input
id={field().name}
name={field().name}
value={field().state.value}
type="number"
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
/>
{field().state.meta.errors ? (
<em role="alert">{field().state.meta.errors.join(', ')}</em>
) : null}
</>
)}
</form.Field>
同步和异步验证可以共存。例如,可以在同一字段上同时定义 onBlur 和 onBlurAsync
<form.Field
name="age"
validators={{
onBlur: ({ value }) => (value < 13 ? 'You must be at least 13' : undefined),
onBlurAsync: async ({ value }) => {
const currentAge = await fetchCurrentAgeOnProfile()
return value < currentAge ? 'You can only increase the age' : undefined
},
}}
>
{(field) => (
<>
<label for={field().name}>Age:</label>
<input
id={field().name}
name={field().name}
value={field().state.value}
type="number"
onBlur={field().handleBlur}
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
/>
{field().state.meta.errors ? (
<em role="alert">{field().state.meta.errors.join(', ')}</em>
) : null}
</>
)}
</form.Field>
<form.Field
name="age"
validators={{
onBlur: ({ value }) => (value < 13 ? 'You must be at least 13' : undefined),
onBlurAsync: async ({ value }) => {
const currentAge = await fetchCurrentAgeOnProfile()
return value < currentAge ? 'You can only increase the age' : undefined
},
}}
>
{(field) => (
<>
<label for={field().name}>Age:</label>
<input
id={field().name}
name={field().name}
value={field().state.value}
type="number"
onBlur={field().handleBlur}
onInput={(e) => field().handleChange(e.target.valueAsNumber)}
/>
{field().state.meta.errors ? (
<em role="alert">{field().state.meta.errors.join(', ')}</em>
) : null}
</>
)}
</form.Field>
同步验证方法 (onBlur) 首先运行,异步方法 (onBlurAsync) 仅在同步方法 (onBlur) 成功时运行。要更改此行为,请将 asyncAlways 选项设置为 true,异步方法将无论同步方法的结果如何都会运行。
虽然异步调用是在数据库中验证的最佳方式,但在每次击键时运行网络请求是 DDOS 您的数据库的好方法。
相反,我们启用了一种简单的方法来防抖您的 async 调用,只需添加一个属性
<form.Field
name="age"
asyncDebounceMs={500}
validators={{
onChangeAsync: async ({ value }) => {
// ...
},
}}
children={(field) => {
return <>{/* ... */}</>
}}
/>
<form.Field
name="age"
asyncDebounceMs={500}
validators={{
onChangeAsync: async ({ value }) => {
// ...
},
}}
children={(field) => {
return <>{/* ... */}</>
}}
/>
这将以 500 毫秒的延迟防抖每个异步调用。您甚至可以覆盖每个验证属性的此属性
<form.Field
name="age"
asyncDebounceMs={500}
validators={{
onChangeAsyncDebounceMs: 1500,
onChangeAsync: async ({ value }) => {
// ...
},
onBlurAsync: async ({ value }) => {
// ...
},
}}
children={(field) => {
return <>{/* ... */}</>
}}
/>
<form.Field
name="age"
asyncDebounceMs={500}
validators={{
onChangeAsyncDebounceMs: 1500,
onChangeAsync: async ({ value }) => {
// ...
},
onBlurAsync: async ({ value }) => {
// ...
},
}}
children={(field) => {
return <>{/* ... */}</>
}}
/>
这将每 1500 毫秒运行一次 onChangeAsync,而 onBlurAsync 将每 500 毫秒运行一次。
虽然函数在验证方面提供了更大的灵活性和自定义性,但它们可能有点冗长。为了帮助解决这个问题,有一些库提供基于 schema 的验证,使简写和类型严格的验证变得更加容易。您还可以为整个表单定义单个 schema 并将其传递给表单级别,错误将自动传播到字段。
TanStack Form 原生支持所有遵循 Standard Schema 规范的库,最值得注意的是
注意:请确保使用最新版本的 schema 库,因为旧版本可能尚不支持 Standard Schema。
要使用这些库中的 schema,您可以将它们传递给 validators props,就像您使用自定义函数一样
import { z } from 'zod'
// ...
const form = createForm(() => ({
// ...
}))
;<form.Field
name="age"
validators={{
onChange: z.number().gte(13, 'You must be 13 to make an account'),
}}
children={(field) => {
return <>{/* ... */}</>
}}
/>
import { z } from 'zod'
// ...
const form = createForm(() => ({
// ...
}))
;<form.Field
name="age"
validators={{
onChange: z.number().gte(13, 'You must be 13 to make an account'),
}}
children={(field) => {
return <>{/* ... */}</>
}}
/>
表单和字段级别的异步验证也受支持
<form.Field
name="age"
validators={{
onChange: z.number().gte(13, 'You must be 13 to make an account'),
onChangeAsyncDebounceMs: 500,
onChangeAsync: z.number().refine(
async (value) => {
const currentAge = await fetchCurrentAgeOnProfile()
return value >= currentAge
},
{
message: 'You can only increase the age',
},
),
}}
children={(field) => {
return <>{/* ... */}</>
}}
/>
<form.Field
name="age"
validators={{
onChange: z.number().gte(13, 'You must be 13 to make an account'),
onChangeAsyncDebounceMs: 500,
onChangeAsync: z.number().refine(
async (value) => {
const currentAge = await fetchCurrentAgeOnProfile()
return value >= currentAge
},
{
message: 'You can only increase the age',
},
),
}}
children={(field) => {
return <>{/* ... */}</>
}}
/>
当表单提交时,也会运行 onChange、onBlur 等回调,如果表单无效,则会阻止提交。
表单状态对象有一个 canSubmit 标志,当任何字段无效且表单已被触碰时,该标志为 false(canSubmit 在表单被触碰之前为 true,即使某些字段根据其 onChange/onBlur props 在“技术上”无效)。
您可以通过 form.Subscribe 订阅它,并使用该值来例如在表单无效时禁用提交按钮(实际上,禁用的按钮是不可访问的,请改用 aria-disabled)。
const form = createForm(() => ({
/* ... */
}))
return (
/* ... */
// Dynamic submit button
<form.Subscribe
selector={(state) => ({
canSubmit: state.canSubmit,
isSubmitting: state.isSubmitting,
})}
children={(state) => (
<button type="submit" disabled={!state().canSubmit}>
{state().isSubmitting ? '...' : 'Submit'}
</button>
)}
/>
)
const form = createForm(() => ({
/* ... */
}))
return (
/* ... */
// Dynamic submit button
<form.Subscribe
selector={(state) => ({
canSubmit: state.canSubmit,
isSubmitting: state.isSubmitting,
})}
children={(state) => (
<button type="submit" disabled={!state().canSubmit}>
{state().isSubmitting ? '...' : 'Submit'}
</button>
)}
/>
)
您的每周 JavaScript 新闻。每周一免费发送给超过 100,000 名开发者。