JavaScript Avançado

Formulários Complexos em React: React Hook Form e Zod Validation na Prática

9 min de leitura

Formulários Complexos em React: React Hook Form e Zod Validation na Prática

Por Que React Hook Form e Zod Juntos? Quando você trabalha com formulários complexos em React, rapidamente percebe que gerenciar estado, validação e submissão de forma manual é tedioso e propenso a erros. React Hook Form resolve o gerenciamento de estado com performance excepcional, mantendo o formulário desacoplado do componente. Zod é uma biblioteca de validação TypeScript-first que oferece type safety nativo e mensagens de erro estruturadas. Juntas, elas eliminam boilerplate e reduzem bugs em produção. A combinação é poderosa porque React Hook Form é agnóstica sobre validação (você escolhe a estratégia), enquanto Zod fornece schemas declarativos e reutilizáveis. Você define as regras uma única vez e as aproveita tanto no cliente quanto no servidor. Configuração Inicial e Integração Instalação das Dependências O pacote é essencial — ele adapta Zod ao padrão esperado por React Hook Form. Exemplo Básico de Formulário O método vincula campos ao formulário, enquanto valida antes de chamar . Os erros vêm pré-formatados do schema Zod.

<h2>Por Que React Hook Form e Zod Juntos?</h2>

<p>Quando você trabalha com formulários complexos em React, rapidamente percebe que gerenciar estado, validação e submissão de forma manual é tedioso e propenso a erros. <strong>React Hook Form</strong> resolve o gerenciamento de estado com performance excepcional, mantendo o formulário desacoplado do componente. <strong>Zod</strong> é uma biblioteca de validação TypeScript-first que oferece type safety nativo e mensagens de erro estruturadas. Juntas, elas eliminam boilerplate e reduzem bugs em produção.</p>

<p>A combinação é poderosa porque React Hook Form é agnóstica sobre validação (você escolhe a estratégia), enquanto Zod fornece schemas declarativos e reutilizáveis. Você define as regras uma única vez e as aproveita tanto no cliente quanto no servidor.</p>

<h2>Configuração Inicial e Integração</h2>

<h3>Instalação das Dependências</h3>

<pre><code class="language-bash">npm install react-hook-form zod @hookform/resolvers</code></pre>

<p>O pacote <code>@hookform/resolvers</code> é essencial — ele adapta Zod ao padrão esperado por React Hook Form.</p>

<h3>Exemplo Básico de Formulário</h3>

<pre><code class="language-jsx">import { useForm } from &#039;react-hook-form&#039;;

import { zodResolver } from &#039;@hookform/resolvers/zod&#039;;

import { z } from &#039;zod&#039;;

// Schema Zod

const userSchema = z.object({

name: z.string().min(3, &#039;Nome deve ter no mínimo 3 caracteres&#039;),

email: z.string().email(&#039;Email inválido&#039;),

password: z.string().min(8, &#039;Senha deve ter no mínimo 8 caracteres&#039;),

});

type UserFormData = z.infer&lt;typeof userSchema&gt;;

export function UserForm() {

const { register, handleSubmit, formState: { errors } } = useForm&lt;UserFormData&gt;({

resolver: zodResolver(userSchema),

});

const onSubmit = (data: UserFormData) =&gt; {

console.log(&#039;Dados válidos:&#039;, data);

};

return (

&lt;form onSubmit={handleSubmit(onSubmit)}&gt;

&lt;div&gt;

&lt;input {...register(&#039;name&#039;)} placeholder=&quot;Nome&quot; /&gt;

{errors.name &amp;&amp; &lt;span&gt;{errors.name.message}&lt;/span&gt;}

&lt;/div&gt;

&lt;div&gt;

&lt;input {...register(&#039;email&#039;)} type=&quot;email&quot; placeholder=&quot;Email&quot; /&gt;

{errors.email &amp;&amp; &lt;span&gt;{errors.email.message}&lt;/span&gt;}

&lt;/div&gt;

&lt;div&gt;

&lt;input {...register(&#039;password&#039;)} type=&quot;password&quot; placeholder=&quot;Senha&quot; /&gt;

{errors.password &amp;&amp; &lt;span&gt;{errors.password.message}&lt;/span&gt;}

&lt;/div&gt;

&lt;button type=&quot;submit&quot;&gt;Enviar&lt;/button&gt;

&lt;/form&gt;

);

}</code></pre>

<p>O método <code>register</code> vincula campos ao formulário, enquanto <code>handleSubmit</code> valida antes de chamar <code>onSubmit</code>. Os erros vêm pré-formatados do schema Zod.</p>

<h2>Validações Avançadas com Zod</h2>

<h3>Validações Condicionais e Refinamentos</h3>

<p>Formulários reais exigem lógica além de tipos básicos. Zod oferece <code>.refine()</code> e <code>.superRefine()</code> para isso:</p>

<pre><code class="language-jsx">const registroSchema = z.object({

email: z.string().email(),

confirmarEmail: z.string().email(),

tipoUsuario: z.enum([&#039;pessoal&#039;, &#039;empresarial&#039;]),

cnpj: z.string().optional(),

}).refine((data) =&gt; data.email === data.confirmarEmail, {

message: &#039;Emails não correspondem&#039;,

path: [&#039;confirmarEmail&#039;], // Vincula o erro ao campo específico

}).refine((data) =&gt; {

if (data.tipoUsuario === &#039;empresarial&#039; &amp;&amp; !data.cnpj) {

return false;

}

return true;

}, {

message: &#039;CNPJ obrigatório para empresas&#039;,

path: [&#039;cnpj&#039;],

});

export function RegistroForm() {

const { register, handleSubmit, formState: { errors } } = useForm({

resolver: zodResolver(registroSchema),

});

return (

&lt;form onSubmit={handleSubmit((data) =&gt; console.log(data))}&gt;

&lt;input {...register(&#039;email&#039;)} placeholder=&quot;Email&quot; /&gt;

&lt;input {...register(&#039;confirmarEmail&#039;)} placeholder=&quot;Confirme o email&quot; /&gt;

{errors.confirmarEmail &amp;&amp; &lt;span&gt;{errors.confirmarEmail.message}&lt;/span&gt;}

&lt;select {...register(&#039;tipoUsuario&#039;)}&gt;

&lt;option value=&quot;pessoal&quot;&gt;Pessoal&lt;/option&gt;

&lt;option value=&quot;empresarial&quot;&gt;Empresarial&lt;/option&gt;

&lt;/select&gt;

&lt;input {...register(&#039;cnpj&#039;)} placeholder=&quot;CNPJ (se empresa)&quot; /&gt;

{errors.cnpj &amp;&amp; &lt;span&gt;{errors.cnpj.message}&lt;/span&gt;}

&lt;button type=&quot;submit&quot;&gt;Registrar&lt;/button&gt;

&lt;/form&gt;

);

}</code></pre>

<h3>Reutilizando Schemas</h3>

<p>Schemas Zod são reutilizáveis. Você pode compor e estender:</p>

<pre><code class="language-jsx">const enderecoSchema = z.object({

rua: z.string().min(5),

cidade: z.string().min(3),

cep: z.string().regex(/^\d{5}-\d{3}$/, &#039;CEP inválido&#039;),

});

const usuarioComEnderecoSchema = z.object({

nome: z.string().min(3),

endereco: enderecoSchema,

});</code></pre>

<h2>Otimizações e Boas Práticas</h2>

<h3>Validação em Tempo Real e Performance</h3>

<p>React Hook Form valida por padrão apenas na submissão. Para feedback imediato, use <code>mode</code>:</p>

<pre><code class="language-jsx">const { register, watch, formState: { errors } } = useForm({

resolver: zodResolver(schema),

mode: &#039;onChange&#039;, // Valida enquanto digita

});</code></pre>

<blockquote><p><strong>Aviso</strong>: <code>mode: &#039;onChange&#039;</code> valida em cada keystroke. Para formulários complexos, considere <code>onBlur</code> para menos re-renders.</p></blockquote>

<h3>Campos Dinâmicos com <code>useFieldArray</code></h3>

<p>Para listas dinâmicas (múltiplos endereços, telefones), use <code>useFieldArray</code>:</p>

<pre><code class="language-jsx">import { useFieldArray } from &#039;react-hook-form&#039;;

const schema = z.object({

contatos: z.array(z.object({

telefone: z.string().regex(/^\d{10,11}$/, &#039;Telefone inválido&#039;),

})),

});

export function ContatosDinamicos() {

const { register, control, handleSubmit } = useForm({

resolver: zodResolver(schema),

});

const { fields, append, remove } = useFieldArray({

control,

name: &#039;contatos&#039;,

});

return (

&lt;form onSubmit={handleSubmit((data) =&gt; console.log(data))}&gt;

{fields.map((field, index) =&gt; (

&lt;div key={field.id}&gt;

&lt;input {...register(contatos.${index}.telefone)} /&gt;

&lt;button type=&quot;button&quot; onClick={() =&gt; remove(index)}&gt;

Remover

&lt;/button&gt;

&lt;/div&gt;

))}

&lt;button type=&quot;button&quot; onClick={() =&gt; append({ telefone: &#039;&#039; })}&gt;

Adicionar Contato

&lt;/button&gt;

&lt;button type=&quot;submit&quot;&gt;Enviar&lt;/button&gt;

&lt;/form&gt;

);

}</code></pre>

<h3>Integração com API (Side Effects)</h3>

<p>Para chamadas assíncronas após validação:</p>

<pre><code class="language-jsx">const onSubmit = async (data: UserFormData) =&gt; {

try {

const response = await fetch(&#039;/api/users&#039;, {

method: &#039;POST&#039;,

body: JSON.stringify(data),

});

if (!response.ok) throw new Error(&#039;Erro no servidor&#039;);

alert(&#039;Usuário criado com sucesso&#039;);

} catch (error) {

console.error(error);

}

};</code></pre>

<h2>Conclusão</h2>

<p>Você aprendeu que <strong>React Hook Form + Zod</strong> é a combinação ideal para formulários modernos: React Hook Form gerencia estado com eficiência, Zod valida com type safety. Dominar <code>useForm</code>, <code>register</code>, schemas aninhados e <code>useFieldArray</code> te coloca no nível profissional. Na prática, sempre reutilize schemas, escolha o <code>mode</code> correto de validação e teste schemas separadamente do componente.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://react-hook-form.com/" target="_blank" rel="noopener noreferrer">React Hook Form - Documentação Oficial</a></li>

<li><a href="https://zod.dev/" target="_blank" rel="noopener noreferrer">Zod - GitHub e Documentação</a></li>

<li><a href="https://www.npmjs.com/package/@hookform/resolvers" target="_blank" rel="noopener noreferrer">@hookform/resolvers - npm</a></li>

<li><a href="https://www.smashingmagazine.com/2022/09/inline-validation-web-forms-constraint-validation-api/" target="_blank" rel="noopener noreferrer">Building Better Forms in React with React Hook Form</a></li>

<li><a href="https://www.totaltypescript.com/tutorials/zod" target="_blank" rel="noopener noreferrer">TypeScript-First Schema Validation with Zod - Tutorial</a></li>

</ul>

Comentários

Mais em JavaScript Avançado

Padrões Avançados em React: Compound Components, Render Props e HOCs na Prática
Padrões Avançados em React: Compound Components, Render Props e HOCs na Prática

Compound Components Compound Components é um padrão onde componentes trabalha...

Dominando Segurança, Auditoria de Dependências e Hardening Final em Node.js em Projetos Reais
Dominando Segurança, Auditoria de Dependências e Hardening Final em Node.js em Projetos Reais

Segurança em Node.js: Fundamentos e Práticas Essenciais A segurança em aplica...

Como Usar Testes de Integração em Node.js: Supertest, Banco Real e Fixtures em Produção
Como Usar Testes de Integração em Node.js: Supertest, Banco Real e Fixtures em Produção

Fundamentos de Testes de Integração em Node.js Testes de integração validam o...