Zod descreve como os dados válidos se parecem em tempo de execução e infere o mesmo tipo TypeScript que seu IDE autocompleta. No mobile, isso importa em dobro: TextInput sempre entrega strings, e os usuários esperam erros inline sob o campo que acabaram de deixar - não um modal após tocar em Enviar.
// lib/schemas/profile.ts - compartilhado entre formulário e cliente de APIimport { z } from "zod";export const ProfileSchema = z.object({ displayName: z .string() .trim() .min(1, "Nome de exibição é obrigatório") .max(40, "Mantenha em menos de 40 caracteres"), bio: z .string() .max(280, "Bio deve ter no máximo 280 caracteres") .optional() .or(z.literal("")), age: z .string() .min(1, "Idade é obrigatória") .pipe( z.coerce .number({ invalid_type_error: "Insira um número" }) .int("Apenas números inteiros") .min(13, "Você deve ter pelo menos 13 anos") .max(120, "Insira uma idade realista") ), newsletter: z.boolean().default(false),});export type ProfileValues = z.infer<typeof ProfileSchema>;export const ProfileUpdateSchema = ProfileSchema.pick({ displayName: true, bio: true, newsletter: true,});export type ProfileUpdateValues = z.infer<typeof ProfileUpdateSchema>;
import { z } from "zod";const OrderSchema = z.object({ quantity: z.coerce.number().positive(), note: z.string().optional(),});type OrderInput = z.input<typeof OrderSchema>; // antes de transforms/coercetype OrderOutput = z.output<typeof OrderSchema>; // depois - igual a z.infer quando não há transformstype OrderValues = z.infer<typeof OrderSchema>;
Instale com npx expo install zod @hookform/resolvers react-hook-form.
Exporte Schema, type Values = z.infer<typeof Schema>, e helpers safeParse de um único módulo.
Reutilize schemas de resposta de API com .pick() / .omit() / .extend() para que as formas de lista, detalhe e formulário permaneçam alinhadas.
Duplicar tipos e schemas - Manter uma interface Profile separada de ProfileSchema diverge na primeira alteração de API. Correção: Exporte apenas z.infer<typeof ProfileSchema>.
Validar números sem coerção - z.number() falha quando TextInput entrega "25" como string. Correção:z.coerce.number() ou z.string().regex(/^\d+$/).transform(Number).
.optional() vs string vazia - Campo de bio opcional com "" falha z.string().email() encadeado depois. Correção: Remova espaços e transforme "" → undefined, ou use .or(z.literal("")).
Mostrar erros antes do toque - Validar onChange a cada pressionamento de tecla frustra usuários mobile. Correção:mode: "onTouched" e renderize isTouched && error.
Correspondência de senha no caminho errado - .refine() sem path coloca o erro na raiz do formulário. Correção:path: ["confirmPassword"] para que a mensagem fique sob o campo correto.
Campos de API anuláveis em formulários - Backend envia middleName: null mas o formulário usa "". Correção: Normalize em defaultValues e schema com .nullable().transform(v => v ?? "").
Schema monolítico gigante - Um schema de 40 campos torna os caminhos de erro difíceis de ler. Correção: Componha schemas de etapas e mescle com .merge() para assistentes, ou valide por etapa com .pick().