React & Frontend

Boas Práticas de React Hook Form Avançado: Arrays, Nested Fields e Wizards para Times Ágeis

20 min de leitura

Boas Práticas de React Hook Form Avançado: Arrays, Nested Fields e Wizards para Times Ágeis

Entendendo o Problema: Por Que React Hook Form Avançado? Quando começamos a trabalhar com formulários em React, muitas vezes enfrentamos situações onde um simples ou até mesmo bibliotecas básicas se tornam insuficientes. React Hook Form é uma biblioteca que resolve este problema de forma elegante, mas suas capacidades vão muito além de formulários simples. Trabalhar com arrays dinâmicos, campos aninhados e fluxos de múltiplas etapas (wizards) requer uma compreensão profunda de como a biblioteca gerencia estado e validação. A razão pela qual este tópico é crucial está no fato de que muitos aplicativos reais precisam de formulários sofisticados: cadastros de múltiplos endereços, listas de produtos com variações, ou processos de inscrição em etapas. Se você tentar implementar isso sem entender os conceitos avançados da biblioteca, acabará com código desorganizado, difícil de manter e propenso a bugs. Vamos resolver isso de forma prática e estruturada. Trabalhando com Arrays e Campos Dinâmicos O Hook : Sua Ferramenta Principal O é o coração

<h2>Entendendo o Problema: Por Que React Hook Form Avançado?</h2>

<p>Quando começamos a trabalhar com formulários em React, muitas vezes enfrentamos situações onde um simples <code>useState</code> ou até mesmo bibliotecas básicas se tornam insuficientes. React Hook Form é uma biblioteca que resolve este problema de forma elegante, mas suas capacidades vão muito além de formulários simples. Trabalhar com arrays dinâmicos, campos aninhados e fluxos de múltiplas etapas (wizards) requer uma compreensão profunda de como a biblioteca gerencia estado e validação.</p>

<p>A razão pela qual este tópico é crucial está no fato de que muitos aplicativos reais precisam de formulários sofisticados: cadastros de múltiplos endereços, listas de produtos com variações, ou processos de inscrição em etapas. Se você tentar implementar isso sem entender os conceitos avançados da biblioteca, acabará com código desorganizado, difícil de manter e propenso a bugs. Vamos resolver isso de forma prática e estruturada.</p>

<h2>Trabalhando com Arrays e Campos Dinâmicos</h2>

<h3>O Hook <code>useFieldArray</code>: Sua Ferramenta Principal</h3>

<p>O <code>useFieldArray</code> é o coração para trabalhar com listas de campos no React Hook Form. Ele permite adicionar, remover e manipular campos de forma reativa, mantendo o estado sincronizado com o formulário. Diferentemente de gerenciar um array com <code>useState</code>, o <code>useFieldArray</code> integra-se perfeitamente com a validação e o sistema de erros da biblioteca.</p>

<p>Vejamos um exemplo prático: um formulário onde o usuário pode adicionar múltiplos números de telefone. Começamos configurando o <code>useForm</code> com um valor padrão que inclui um array:</p>

<pre><code class="language-jsx">import React from &#039;react&#039;;

import { useForm, useFieldArray, Controller } from &#039;react-hook-form&#039;;

export function MultiPhoneForm() {

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

defaultValues: {

phones: [

{ number: &#039;&#039;, type: &#039;celular&#039; }

]

}

});

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

control,

name: &#039;phones&#039;

});

const onSubmit = (data) =&gt; {

console.log(&#039;Dados do formulário:&#039;, data);

};

return (

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

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

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

&lt;Controller

name={phones.${index}.number}

control={control}

rules={{

required: &#039;Telefone é obrigatório&#039;,

pattern: {

value: /^[0-9\-\(\) ]+$/,

message: &#039;Telefone inválido&#039;

}

}}

render={({ field }) =&gt; (

&lt;input

{...field}

placeholder=&quot;(11) 99999-9999&quot;

type=&quot;tel&quot;

/&gt;

)}

/&gt;

{errors.phones?.[index]?.number &amp;&amp; (

&lt;span&gt;{errors.phones[index].number.message}&lt;/span&gt;

)}

&lt;Controller

name={phones.${index}.type}

control={control}

render={({ field }) =&gt; (

&lt;select {...field}&gt;

&lt;option value=&quot;celular&quot;&gt;Celular&lt;/option&gt;

&lt;option value=&quot;residencial&quot;&gt;Residencial&lt;/option&gt;

&lt;option value=&quot;comercial&quot;&gt;Comercial&lt;/option&gt;

&lt;/select&gt;

)}

/&gt;

{fields.length &gt; 1 &amp;&amp; (

&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({ number: &#039;&#039;, type: &#039;celular&#039; })}

&gt;

Adicionar Telefone

&lt;/button&gt;

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

&lt;/form&gt;

);

}</code></pre>

<h3>Validação e Comportamento Avançado</h3>

<p>Um detalhe importante que muitos iniciantes ignoram: quando você remove um campo com <code>remove()</code>, o React Hook Form automaticamente reorganiza os índices. Isso significa que você não precisa se preocupar com buracos no array ou IDs desalinhados. Porém, é crucial usar a propriedade <code>id</code> do <code>field</code> como chave do React, não o índice, para evitar problemas de re-render.</p>

<p>Quando precisamos validar o array como um todo (por exemplo, garantir que pelo menos um telefone existe), fazemos isso ao nível do formulário, não do campo individual:</p>

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

defaultValues: {

phones: [{ number: &#039;&#039;, type: &#039;celular&#039; }]

},

resolver: async (data) =&gt; {

const errors = {};

if (!data.phones || data.phones.length === 0) {

errors.phones = {

message: &#039;Adicione pelo menos um telefone&#039;

};

}

if (data.phones &amp;&amp; data.phones.every(p =&gt; !p.number)) {

errors.phones = {

message: &#039;Adicione pelo menos um número válido&#039;

};

}

return { values: data, errors };

}

});</code></pre>

<h2>Campos Aninhados e Estruturas Complexas</h2>

<h3>Construindo Estruturas Hierárquicas</h3>

<p>Quando seus formulários precisam de múltiplos níveis de aninhamento, a coisa fica mais interessante. Um exemplo clássico é um cadastro de usuário com múltiplos endereços, e cada endereço contém coordenadas GPS e histórico de mudanças. A estratégia aqui é usar <code>watch</code> para monitorar valores e o padrão de nomenclatura de pontos do React Hook Form.</p>

<pre><code class="language-jsx">import React from &#039;react&#039;;

import { useForm, useFieldArray, Controller, watch } from &#039;react-hook-form&#039;;

export function UserAddressForm() {

const { control, handleSubmit, watch: watchForm } = useForm({

defaultValues: {

user: {

name: &#039;&#039;,

email: &#039;&#039;

},

addresses: [

{

street: &#039;&#039;,

number: &#039;&#039;,

complement: &#039;&#039;,

geolocation: {

latitude: &#039;&#039;,

longitude: &#039;&#039;

},

isDefault: false

}

]

}

});

const { fields: addressFields, append: appendAddress, remove: removeAddress } = useFieldArray({

control,

name: &#039;addresses&#039;

});

const watchedAddresses = watchForm(&#039;addresses&#039;);

const onSubmit = (data) =&gt; {

console.log(&#039;Dados completos:&#039;, data);

};

return (

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

&lt;fieldset&gt;

&lt;legend&gt;Dados Pessoais&lt;/legend&gt;

&lt;Controller

name=&quot;user.name&quot;

control={control}

rules={{ required: &#039;Nome é obrigatório&#039; }}

render={({ field }) =&gt; (

&lt;input {...field} placeholder=&quot;Nome completo&quot; /&gt;

)}

/&gt;

&lt;Controller

name=&quot;user.email&quot;

control={control}

rules={{

required: &#039;E-mail é obrigatório&#039;,

pattern: {

value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,

message: &#039;E-mail inválido&#039;

}

}}

render={({ field }) =&gt; (

&lt;input {...field} type=&quot;email&quot; placeholder=&quot;seu@email.com&quot; /&gt;

)}

/&gt;

&lt;/fieldset&gt;

&lt;fieldset&gt;

&lt;legend&gt;Endereços&lt;/legend&gt;

{addressFields.map((addressField, addressIndex) =&gt; (

&lt;div key={addressField.id} style={{ border: &#039;1px solid #ccc&#039;, padding: &#039;1rem&#039;, marginBottom: &#039;1rem&#039; }}&gt;

&lt;h4&gt;Endereço {addressIndex + 1}&lt;/h4&gt;

&lt;Controller

name={addresses.${addressIndex}.street}

control={control}

rules={{ required: &#039;Rua é obrigatória&#039; }}

render={({ field }) =&gt; (

&lt;input {...field} placeholder=&quot;Rua&quot; /&gt;

)}

/&gt;

&lt;Controller

name={addresses.${addressIndex}.number}

control={control}

rules={{ required: &#039;Número é obrigatório&#039; }}

render={({ field }) =&gt; (

&lt;input {...field} placeholder=&quot;Número&quot; type=&quot;text&quot; /&gt;

)}

/&gt;

&lt;Controller

name={addresses.${addressIndex}.complement}

control={control}

render={({ field }) =&gt; (

&lt;input {...field} placeholder=&quot;Complemento (opcional)&quot; /&gt;

)}

/&gt;

&lt;fieldset&gt;

&lt;legend&gt;Geolocalização&lt;/legend&gt;

&lt;Controller

name={addresses.${addressIndex}.geolocation.latitude}

control={control}

rules={{

pattern: {

value: /^-?\d+(\.\d+)?$/,

message: &#039;Latitude inválida&#039;

}

}}

render={({ field }) =&gt; (

&lt;input {...field} placeholder=&quot;Latitude&quot; /&gt;

)}

/&gt;

&lt;Controller

name={addresses.${addressIndex}.geolocation.longitude}

control={control}

rules={{

pattern: {

value: /^-?\d+(\.\d+)?$/,

message: &#039;Longitude inválida&#039;

}

}}

render={({ field }) =&gt; (

&lt;input {...field} placeholder=&quot;Longitude&quot; /&gt;

)}

/&gt;

&lt;/fieldset&gt;

&lt;Controller

name={addresses.${addressIndex}.isDefault}

control={control}

render={({ field }) =&gt; (

&lt;label&gt;

&lt;input {...field} type=&quot;checkbox&quot; /&gt;

Endereço padrão

&lt;/label&gt;

)}

/&gt;

{addressFields.length &gt; 1 &amp;&amp; (

&lt;button

type=&quot;button&quot;

onClick={() =&gt; removeAddress(addressIndex)}

&gt;

Remover endereço

&lt;/button&gt;

)}

&lt;/div&gt;

))}

&lt;button

type=&quot;button&quot;

onClick={() =&gt; appendAddress({

street: &#039;&#039;,

number: &#039;&#039;,

complement: &#039;&#039;,

geolocation: { latitude: &#039;&#039;, longitude: &#039;&#039; },

isDefault: false

})}

&gt;

Adicionar Endereço

&lt;/button&gt;

&lt;/fieldset&gt;

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

&lt;/form&gt;

);

}</code></pre>

<h3>Validação Condicional em Estruturas Aninhadas</h3>

<p>A parte desafiadora aqui é validar campos baseado em outros campos do mesmo nível ou de níveis diferentes. Por exemplo: se <code>isDefault</code> é true, os campos de geolocalização devem ser obrigatórios. Para isso, usamos a função de resolver do Yup ou Zod, ou validação personalizada:</p>

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

import { yupResolver } from &#039;@hookform/resolvers/yup&#039;;

import * as yup from &#039;yup&#039;;

const validationSchema = yup.object({

user: yup.object({

name: yup.string().required(&#039;Nome obrigatório&#039;),

email: yup.string().email().required(&#039;E-mail obrigatório&#039;)

}),

addresses: yup.array().of(

yup.object({

street: yup.string().required(&#039;Rua obrigatória&#039;),

number: yup.string().required(&#039;Número obrigatório&#039;),

geolocation: yup.object().when(&#039;isDefault&#039;, {

is: true,

then: (schema) =&gt; schema.shape({

latitude: yup.string().required(&#039;Latitude obrigatória quando endereço é padrão&#039;),

longitude: yup.string().required(&#039;Longitude obrigatória quando endereço é padrão&#039;)

}),

otherwise: (schema) =&gt; schema

})

})

)

});

export function UserAddressFormWithValidation() {

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

resolver: yupResolver(validationSchema),

defaultValues: {

user: { name: &#039;&#039;, email: &#039;&#039; },

addresses: [{ street: &#039;&#039;, number: &#039;&#039;, complement: &#039;&#039;, geolocation: { latitude: &#039;&#039;, longitude: &#039;&#039; }, isDefault: false }]

}

});

const onSubmit = (data) =&gt; {

console.log(&#039;Dados validados:&#039;, data);

};

return (

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

{/ Renderizar campos aqui /}

&lt;/form&gt;

);

}</code></pre>

<h2>Implementando Wizards (Formulários em Múltiplas Etapas)</h2>

<h3>Arquitetura de um Wizard</h3>

<p>Um wizard é um formulário dividido em várias etapas, onde o usuário progride de uma para a próxima. A chave para implementar isso corretamente com React Hook Form é manter um único <code>useForm</code> para o formulário inteiro, mas controlar qual etapa está sendo exibida. Isso garante que o estado seja preservado enquanto o usuário navega entre as etapas.</p>

<pre><code class="language-jsx">import React, { useState } from &#039;react&#039;;

import { useForm, Controller } from &#039;react-hook-form&#039;;

export function RegistrationWizard() {

const [currentStep, setCurrentStep] = useState(1);

const totalSteps = 3;

const { control, handleSubmit, trigger, formState: { errors }, watch } = useForm({

mode: &#039;onBlur&#039;,

defaultValues: {

// Passo 1: Informações Pessoais

firstName: &#039;&#039;,

lastName: &#039;&#039;,

email: &#039;&#039;,

// Passo 2: Endereço

street: &#039;&#039;,

number: &#039;&#039;,

city: &#039;&#039;,

state: &#039;&#039;,

zipCode: &#039;&#039;,

// Passo 3: Confirmação

acceptTerms: false,

newsletter: false

}

});

const watchedValues = watch();

const handleNext = async () =&gt; {

const fieldsToValidate = getFieldsByStep(currentStep);

const isValid = await trigger(fieldsToValidate);

if (isValid) {

setCurrentStep((prev) =&gt; Math.min(prev + 1, totalSteps));

}

};

const handleBack = () =&gt; {

setCurrentStep((prev) =&gt; Math.max(prev - 1, 1));

};

const getFieldsByStep = (step) =&gt; {

switch (step) {

case 1:

return [&#039;firstName&#039;, &#039;lastName&#039;, &#039;email&#039;];

case 2:

return [&#039;street&#039;, &#039;number&#039;, &#039;city&#039;, &#039;state&#039;, &#039;zipCode&#039;];

case 3:

return [&#039;acceptTerms&#039;];

default:

return [];

}

};

const onSubmit = (data) =&gt; {

console.log(&#039;Formulário completo:&#039;, data);

// Enviar dados para API

};

return (

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

&lt;div style={{ marginBottom: &#039;2rem&#039; }}&gt;

&lt;div style={{ display: &#039;flex&#039;, gap: &#039;0.5rem&#039; }}&gt;

{[1, 2, 3].map((step) =&gt; (

&lt;div

key={step}

style={{

width: &#039;2rem&#039;,

height: &#039;2rem&#039;,

borderRadius: &#039;50%&#039;,

display: &#039;flex&#039;,

alignItems: &#039;center&#039;,

justifyContent: &#039;center&#039;,

backgroundColor: currentStep &gt;= step ? &#039;#007bff&#039; : &#039;#e9ecef&#039;,

color: currentStep &gt;= step ? &#039;white&#039; : &#039;#000&#039;,

fontWeight: &#039;bold&#039;

}}

&gt;

{step}

&lt;/div&gt;

))}

&lt;/div&gt;

&lt;/div&gt;

{currentStep === 1 &amp;&amp; (

&lt;fieldset&gt;

&lt;legend&gt;Passo 1: Informações Pessoais&lt;/legend&gt;

&lt;div&gt;

&lt;label&gt;Primeiro Nome&lt;/label&gt;

&lt;Controller

name=&quot;firstName&quot;

control={control}

rules={{ required: &#039;Primeiro nome é obrigatório&#039; }}

render={({ field }) =&gt; &lt;input {...field} type=&quot;text&quot; /&gt;}

/&gt;

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

&lt;/div&gt;

&lt;div&gt;

&lt;label&gt;Último Nome&lt;/label&gt;

&lt;Controller

name=&quot;lastName&quot;

control={control}

rules={{ required: &#039;Último nome é obrigatório&#039; }}

render={({ field }) =&gt; &lt;input {...field} type=&quot;text&quot; /&gt;}

/&gt;

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

&lt;/div&gt;

&lt;div&gt;

&lt;label&gt;E-mail&lt;/label&gt;

&lt;Controller

name=&quot;email&quot;

control={control}

rules={{

required: &#039;E-mail é obrigatório&#039;,

pattern: {

value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,

message: &#039;E-mail inválido&#039;

}

}}

render={({ field }) =&gt; &lt;input {...field} type=&quot;email&quot; /&gt;}

/&gt;

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

&lt;/div&gt;

&lt;/fieldset&gt;

)}

{currentStep === 2 &amp;&amp; (

&lt;fieldset&gt;

&lt;legend&gt;Passo 2: Endereço&lt;/legend&gt;

&lt;div&gt;

&lt;label&gt;Rua&lt;/label&gt;

&lt;Controller

name=&quot;street&quot;

control={control}

rules={{ required: &#039;Rua é obrigatória&#039; }}

render={({ field }) =&gt; &lt;input {...field} type=&quot;text&quot; /&gt;}

/&gt;

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

&lt;/div&gt;

&lt;div&gt;

&lt;label&gt;Número&lt;/label&gt;

&lt;Controller

name=&quot;number&quot;

control={control}

rules={{ required: &#039;Número é obrigatório&#039; }}

render={({ field }) =&gt; &lt;input {...field} type=&quot;text&quot; /&gt;}

/&gt;

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

&lt;/div&gt;

&lt;div&gt;

&lt;label&gt;Cidade&lt;/label&gt;

&lt;Controller

name=&quot;city&quot;

control={control}

rules={{ required: &#039;Cidade é obrigatória&#039; }}

render={({ field }) =&gt; &lt;input {...field} type=&quot;text&quot; /&gt;}

/&gt;

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

&lt;/div&gt;

&lt;div&gt;

&lt;label&gt;Estado&lt;/label&gt;

&lt;Controller

name=&quot;state&quot;

control={control}

rules={{ required: &#039;Estado é obrigatório&#039; }}

render={({ field }) =&gt; (

&lt;select {...field}&gt;

&lt;option value=&quot;&quot;&gt;Selecione&lt;/option&gt;

&lt;option value=&quot;SP&quot;&gt;São Paulo&lt;/option&gt;

&lt;option value=&quot;RJ&quot;&gt;Rio de Janeiro&lt;/option&gt;

&lt;option value=&quot;MG&quot;&gt;Minas Gerais&lt;/option&gt;

&lt;/select&gt;

)}

/&gt;

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

&lt;/div&gt;

&lt;div&gt;

&lt;label&gt;CEP&lt;/label&gt;

&lt;Controller

name=&quot;zipCode&quot;

control={control}

rules={{

required: &#039;CEP é obrigatório&#039;,

pattern: {

value: /^\d{5}-?\d{3}$/,

message: &#039;CEP inválido&#039;

}

}}

render={({ field }) =&gt; &lt;input {...field} type=&quot;text&quot; /&gt;}

/&gt;

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

&lt;/div&gt;

&lt;/fieldset&gt;

)}

{currentStep === 3 &amp;&amp; (

&lt;fieldset&gt;

&lt;legend&gt;Passo 3: Confirmação&lt;/legend&gt;

&lt;div&gt;

&lt;h3&gt;Revise seus dados:&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Nome:&lt;/strong&gt; {watchedValues.firstName} {watchedValues.lastName}&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E-mail:&lt;/strong&gt; {watchedValues.email}&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Endereço:&lt;/strong&gt; {watchedValues.street}, {watchedValues.number} - {watchedValues.city}, {watchedValues.state}&lt;/p&gt;

&lt;/div&gt;

&lt;div&gt;

&lt;Controller

name=&quot;acceptTerms&quot;

control={control}

rules={{ required: &#039;Você deve aceitar os termos&#039; }}

render={({ field }) =&gt; (

&lt;label&gt;

&lt;input {...field} type=&quot;checkbox&quot; /&gt;

Eu aceito os termos e condições

&lt;/label&gt;

)}

/&gt;

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

&lt;/div&gt;

&lt;div&gt;

&lt;Controller

name=&quot;newsletter&quot;

control={control}

render={({ field }) =&gt; (

&lt;label&gt;

&lt;input {...field} type=&quot;checkbox&quot; /&gt;

Receber notícias por e-mail

&lt;/label&gt;

)}

/&gt;

&lt;/div&gt;

&lt;/fieldset&gt;

)}

&lt;div style={{ display: &#039;flex&#039;, gap: &#039;1rem&#039;, marginTop: &#039;2rem&#039; }}&gt;

{currentStep &gt; 1 &amp;&amp; (

&lt;button type=&quot;button&quot; onClick={handleBack}&gt;

Voltar

&lt;/button&gt;

)}

{currentStep &lt; totalSteps &amp;&amp; (

&lt;button type=&quot;button&quot; onClick={handleNext}&gt;

Próximo

&lt;/button&gt;

)}

{currentStep === totalSteps &amp;&amp; (

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

Finalizar Registro

&lt;/button&gt;

)}

&lt;/div&gt;

&lt;/form&gt;

);

}</code></pre>

<h3>Validação Contextual em Wizards</h3>

<p>Um aspecto crucial que muitos desenvolvedores esquecem é que em um wizard, a validação deve ser progressiva. Quando o usuário clica em &quot;Próximo&quot;, você não valida apenas o passo atual, mas todos os passos anteriores também. Use <code>trigger()</code> com um array de campos específicos para validar somente o que é necessário naquele momento:</p>

<pre><code class="language-jsx">// Validar apenas os campos do passo atual antes de avançar

const handleNext = async () =&gt; {

const fieldsToValidate = getFieldsByStep(currentStep);

const isValid = await trigger(fieldsToValidate);

if (isValid) {

setCurrentStep((prev) =&gt; Math.min(prev + 1, totalSteps));

}

};

// Ao submeter no final, trigger() sem argumentos valida TUDO

const onSubmit = (data) =&gt; {

console.log(&#039;Dados validados completamente:&#039;, data);

};</code></pre>

<h2>Conclusão</h2>

<p>Nesta aula, você aprendeu três conceitos fundamentais que transformam sua capacidade de trabalhar com formulários complexos em React. Primeiro, o <code>useFieldArray</code> não é apenas uma forma de iterar sobre arrays, mas um sistema inteligente de sincronização entre o estado do formulário e a interface, eliminando a necessidade de gerenciar índices manualmente. Segundo, estruturas aninhadas exigem uma nomenclatura cuidadosa usando notação de ponto e uma estratégia clara de validação condicional, que você conquista com Yup, Zod ou resolvers customizados. Terceiro, wizards são implementados corretamente quando você mantém um único <code>useForm</code> para toda a jornada, validando seletivamente por etapa e preservando o estado do usuário enquanto ele navega, não deixando que o desespero de um passo 2 rejeitado apague os dados do passo 1.</p>

<p>A verdadeira maestria em React Hook Form avançado vem de entender que a biblioteca não apenas gerencia formulários, mas oferece primitivos poderosos para sincronizar estado complexo entre UI e validação. Use esses conhecimentos não apenas para resolver os problemas apresentados aqui, mas como fundação para casos ainda mais sofisticados que você encontrará em sua carreira.</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://react-hook-form.com/api/usefieldarray" target="_blank" rel="noopener noreferrer">React Hook Form - useFieldArray API</a></li>

<li><a href="https://github.com/jquense/yup" target="_blank" rel="noopener noreferrer">Yup - Schema Validation</a></li>

<li><a href="https://react-hook-form.com/form-builder" target="_blank" rel="noopener noreferrer">React Hook Form com TypeScript - Exemplo Avançado</a></li>

<li><a href="https://www.smashingmagazine.com/2021/10/react-hook-form/" target="_blank" rel="noopener noreferrer">Building Complex Forms with React Hook Form - Article</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em React & Frontend

Guia Completo de React Query Avançado: Cache, Stale Time, Prefetch e Optimistic UI
Guia Completo de React Query Avançado: Cache, Stale Time, Prefetch e Optimistic UI

Entendendo o Cache e sua Importância no React Query O cache é o coração do Re...

Segurança em Aplicações React: XSS, CSRF e Content Security Policy na Prática
Segurança em Aplicações React: XSS, CSRF e Content Security Policy na Prática

Introdução: O Cenário de Ameaças em Aplicações React Quando você constrói uma...

Internacionalização em React: react-i18next e Formatação de Dados na Prática
Internacionalização em React: react-i18next e Formatação de Dados na Prática

Entendendo Internacionalização em Aplicações React Internacionalização (i18n)...