TanStack Form: Formulários Type-Safe
Aula 4 de 5
Formulários com TanStack Form
O TanStack Form oferece gerenciamento de formulários headless com validação, arrays, campos aninhados e integração com bibliotecas de schema como Zod e Yup.
Configuração Inicial
npm install @tanstack/react-form
useForm Básico
import { useForm } from '@tanstack/react-form'
import { z } from 'zod'
function SignupForm() {
const form = useForm({
defaultValues: {
name: '',
email: '',
age: 0,
},
onSubmit: async ({ value }) => {
await fetch('/api/users', { method: 'POST', body: JSON.stringify(value) })
},
})
return (
<form onSubmit={form.handleSubmit()}>
<form.Field
name="name"
validators={{
onChange: ({ value }) =>
value.length < 3 ? 'Nome precisa ter ao menos 3 caracteres' : undefined,
}}
>
{field => (
<div>
<label htmlFor={field.name}>Nome</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onChange={e => field.handleChange(e.target.value)}
/>
{field.state.meta.errors && (
<p className="error">{field.state.meta.errors.join(', ')}</p>
)}
</div>
)}
</form.Field>
<form.Field name="email">
{field => (
<div>
<label htmlFor={field.name}>E-mail</label>
<input
id={field.name}
value={field.state.value}
onChange={e => field.handleChange(e.target.value)}
/>
</div>
)}
</form.Field>
<button type="submit">Cadastrar</button>
</form>
)
}
formOptions — Estado Compartilhado
import { formOptions } from '@tanstack/react-form'
const signupFormOpts = formOptions({
defaultValues: { name: '', email: '', age: 0 },
validators: {
onSubmit: ({ value }) => {
const parsed = signupSchema.safeParse(value)
return parsed.success ? undefined : parsed.error.issues.map(i => i.message).join(', ')
},
},
})
// Em qualquer componente:
const form = useForm({ ...signupFormOpts })
Validação com Zod
import { z } from 'zod'
const userSchema = z.object({
name: z.string().min(3, 'Mínimo 3 caracteres'),
email: z.string().email('E-mail inválido'),
age: z.number().min(18, 'Deve ser maior de idade'),
})
const form = useForm({
defaultValues: { name: '', email: '', age: 0 },
validators: {
onSubmit: ({ value }) => {
const result = userSchema.safeParse(value)
return result.success ? undefined : result.error.issues[0].message
},
},
})
Validação Assíncrona
<form.Field
name="email"
validators={{
onChangeAsync: async ({ value }) => {
await new Promise(r => setTimeout(r, 500)) // debounce
const taken = await checkEmailExists(value)
return taken ? 'E-mail já cadastrado' : undefined
},
onChangeAsyncDebounceMs: 500,
}}
>
{field => <input value={field.state.value} onChange={e => field.handleChange(e.target.value)} />}
</form.Field>
Arrays e Campos Dinâmicos
function PhoneForm() {
const form = useForm({
defaultValues: { phones: [''] },
})
return (
<form.Field name="phones" mode="array">
{field => (
<div>
{field.state.value.map((_, i) => (
<form.Field key={i} name={`phones[${i}]`}>
{subField => (
<div>
<input
value={subField.state.value}
onChange={e => subField.handleChange(e.target.value)}
/>
<button type="button" onClick={() => field.removeValue(i)}>Remover</button>
</div>
)}
</form.Field>
))}
<button type="button" onClick={() => field.pushValue('')}>Adicionar telefone</button>
</div>
)}
</form.Field>
)
}
Custom Components
import { withFormField } from '@tanstack/react-form'
const InputField = withFormField({
component: ({ field, label }) => (
<div>
<label>{label}</label>
<input
value={field.state.value}
onChange={e => field.handleChange(e.target.value)}
/>
{field.state.meta.errors && (
<span className="error">{field.state.meta.errors[0]}</span>
)}
</div>
),
})
// Uso:
<InputField name="name" label="Nome" />
Form State
const form = useForm({ defaultValues: { name: '' } })
// Acessar estado global
const isSubmitting = form.state.isSubmitting
const errors = form.state.errors
const values = form.state.values
const canSubmit = form.state.canSubmit
// Reset
form.reset()
Lab: Formulário Multi-etapa
npm create vite@latest form-demo -- --template react-ts
cd form-demo
npm install @tanstack/react-form zod
// Construa:
// 1. Formulário de cadastro com 4+ campos (texto, email, número, select)
// 2. Validação síncrona com Zod
// 3. Um campo com validação assíncrona (ex: verificar username)
// 4. Lista dinâmica de skills (array field)
// 5. Submit com feedback visual (isSubmitting)
// 6. Botão de reset
TanStack Form torna formulários complexos previsíveis com validação declarativa, tipagem forte e suporte a cenários avançados como arrays e validação assíncrona.