Manipulando Formulários

Manipulando Formulários

Acredita-se que mais de 70% das aplicações da web contam com algum tipo de formulário de envio, como um campo de texto, um campo de área, caixas seletoras, botões de opção e entre outros estilos de formulários.

A verdade é que durante a sua jornada como desenvolvedor ReactJS, na maior parte das vezes, você vai precisa manipular formulários dentro das telas do seu projeto 🤓

Portanto, é de vital importância que você dê um foco especial nesta lição da jornada, e tente absorver todos os conteúdos e insights presentes por aqui, beleza?

Dito isto, vamos iniciar o conteúdo desta lição 😎

Criando seu projeto de testes

Antes de começarmos, vamos criar um novo projeto em ReactJS dedicado a esta lição. No meu caso criei um novo projeto chamado de manipulando-formularios:    

npx create-react-app manipulando-formularios

Após isso, não se esqueça de fazer aquela limpeza do código, e também aquela organizada nas pastas do src (organização por funcionalidade) 😁  

O que são formulários?

No mundo da tecnologia, um formulário nada mais é do que uma ferramenta digital que coleta informações de usuários por meio de campos predefinidos, facilitando a interação e a coleta de dados de forma organizada e eficiente.

Ou seja, qualquer campo que podemos interagir, seja digitando um texto, fazendo upload de um arquivo ou selecionando uma opção, é considerado um formulário.

Legal, em lições anteriores nós já vimos como manipular esses formulários, seja por meio dos atributos onChange e onSubmit, ou usando alguns hooks do ReactJS como useStatee o useRef.

Hoje, nós iremos nos aprofundar ainda mais na manipualção desses formulários com diversos exemplos diferentes 🙂

Manipulando Formulários em Componentes de Classe

Sim, diferente do que você possa estar pensando, ainda existem muitas aplicações que fazem o uso de componentes de classe, então é uma boa pedida aprendermos também como manipular os formulários que existem dentro desses componentes.

Para começar, vamos criar um novo componente chamado de FormularioClasse1:

FormularioClasse1 > index.jsx:

import React, { Component } from 'react';

class FormularioClasse1 extends Component{

 constructor(props){
 super(props)
 this.state = {
 email: 'meu@email.com',
 senha: '1234567890'
 }

 this.trocaEmail = this.trocaEmail.bind(this);
 }

 trocaEmail(event){
 let valorDigitado = event.target.value;
 this.setState({email: valorDigitado});
 }

 render(){
 return(
 <div>
 <h2>Faça seu Login:</h2>
 Email: <input type="email" name="email" value={this.state.email} onChange={this.trocaEmail} /><br/>
 Senha: <input type="password" name="senha" value={this.state.senha} onChange={ (event) => this.setState({senha: event.target.value}) } />

 <div>
 <p>Email: {this.state.email}</p>
 <p>Senha: {this.state.senha}</p>
 </div>
 </div>
 );
 }
}

export default FormularioClasse1;

E não se esqueça de importar esse componente para dentro do App.js:

import FormularioClasse1 from "./components/FormularioClasse1";

export default function App() {
 return (
 <div>
 <FormularioClasse1 />
 </div>
 );
}

O código do componente FormularioClasse1 não é tão diferente dos exemplos que vimos em lições anteriores.

No caso dele, temos o método construtor (constructor) que é responsável por definir as propriedades de estado (props) que serão usados pelo componente.

Também foi declarado um objeto com valores pré-definidos dentro da chave state, como:

  • nome = meu@email.com
  • senha = 123456789

Por fim, vinculamos ao método bind o contexto do método trocaEmail ao componente atual, isso é necessário quando você define um método em uma classe e deseja usá-lo como um callback em algum lugar, que no nosso caso será usado dentro do JSX (dentro do return).

Em seguida declaramos um novo método chamado de trocaEmail, que será responsável por receber todas as modificações do atributo onChange que por sua vez esta conectado no campo de e-mail, de modo a atualizar o estado da chave de email.

Uma outra possibilidade de se fazer essa mesma atualização, sem a necessidade de fazer bind, é declarando o setState diretamente dentro do atributo onChange, como já está acontecendo no campo de senha, observe:

<input type="password" name="senha" value={this.state.senha} onChange={ (event) => this.setState({senha: event.target.value}) } />

Legal, até então nós vimos como manipular formulários que fazem o uso de campos de textos (<input>), mas você e eu sabemos que não existem somente os campos de textos, como também caixas seletoras, campos de upload, botões e muito mais.

Para testar todos os tipos, vamos criar um novo componente chamado de FormularioClasse2.

FormularioClasse2 > index.jsx:

import React, { Component } from 'react';

class FormularioClasse2 extends Component {
 constructor(props) {
 super(props);
 this.state = {
 inputValue: '',
 selectValue: '',
 textareaValue: '',
 file: null,
 checkboxValue: false,
 dateValue: '',
 rangeValue: 0
 };
 }

 handleInputChange = (event) => {
 this.setState({ inputValue: event.target.value });
 }

 handleSelectChange = (event) => {
 this.setState({ selectValue: event.target.value });
 }

 handleTextareaChange = (event) => {
 this.setState({ textareaValue: event.target.value });
 }

 handleFileChange = (event) => {
 this.setState({ file: event.target.files[0] });
 }

 handleCheckboxChange = (event) => {
 this.setState({ checkboxValue: event.target.checked });
 }

 handleDateChange = (event) => {
 this.setState({ dateValue: event.target.value });
 }

 handleRangeChange = (event) => {
 this.setState({ rangeValue: event.target.value });
 }

 handleSubmit = (event) => {
 event.preventDefault();
 // Aqui você pode fazer o que quiser com os valores do formulário
 console.log('Valores do formulário:', this.state);
 }

 render() {
 return (
 <form onSubmit={this.handleSubmit}>
 <label>
 Input:
 <input
 type="text"
 value={this.state.inputValue}
 onChange={this.handleInputChange}
 />
 </label>
 <br />
 <label>
 Select:
 <select
 value={this.state.selectValue}
 onChange={this.handleSelectChange}
 >
 <option value="opcao1">Opção 1</option>
 <option value="opcao2">Opção 2</option>
 <option value="opcao3">Opção 3</option>
 </select>
 </label>
 <br />
 <label>
 Textarea:
 <textarea
 value={this.state.textareaValue}
 onChange={this.handleTextareaChange}
 />
 </label>
 <br />
 <label>
 Upload:
 <input
 type="file"
 onChange={this.handleFileChange}
 />
 </label>
 <br />
 <label>
 Checkbox:
 <input
 type="checkbox"
 checked={this.state.checkboxValue}
 onChange={this.handleCheckboxChange}
 />
 </label>
 <br />
 <label>
 Data:
 <input
 type="date"
 value={this.state.dateValue}
 onChange={this.handleDateChange}
 />
 </label>
 <br />
 <label>
 Range:
 <input
 type="range"
 value={this.state.rangeValue}
 onChange={this.handleRangeChange}
 min="0"
 max="100"
 />
 </label>
 <br />
 <button type="submit">Enviar</button>
 </form>
 );
 }
}

export default FormularioClasse2;

Perceba que a forma com que estamos definindo os estados para cada um dos formulários não se difere uma das outras. Com exceção do formulário de upload, na qual precisamos de algumas configurações extras para tratar o arquivo enviado.

Veja como ficou o resultado final:

Note que ainda fizemos o uso do onSubmit de modo a chamar o método handleSubmit para tratar o envio final do formulário.

Tratando o upload de imagens em um componente de classe no ReactJS

Para fins exemplares, como eu cheguei a mencionar uma possível exceção para o formulário de upload, nada mais justo que te ensinar a como tratar isso 😅

No exemplo a seguir foi criado um novo componente chamado de FormularioClasse3, cujo o objetivo é pegar a imagem enviada pelo usuário e atualizar o elemento <img>.

FormularioClasse3 > index.jsx:

import React, { Component } from 'react';

class FormularioClasse3 extends Component {
 constructor(props) {
 super(props);
 this.state = {
 imagem: null
 };
 }

 handleImagemChange = (event) => {
 this.setState({ imagem: URL.createObjectURL(event.target.files[0]) });
 }

 render() {
 return (
 <div>
 <input
 type="file"
 onChange={this.handleImagemChange}
 />
 {this.state.imagem && (
 <img src={this.state.imagem} alt="Imagem do usuário" />
 )}
 </div>
 );
 }
}

export default FormularioClasse3;

Veja como ficou o resultado final:

Feito isso, vamos dar uma recapitulada sobre a manipulação de formulários usando os componentes de estado 🤓

Manipulando Formulários em Componentes de Estado

Também vimos em lições passadas como manipular formulários usando os componentes de estado.

Vamos recapitular a forma como isso era feito?

Vamos começar criando um novo componente chamado de FormularioEstado1.

FormularioEstado1 > index.jsx:

import React, { useState } from 'react';

const FormularioClasse1 = () => {
 const [email, setEmail] = useState('meu@email.com');
 const [senha, setSenha] = useState('1234567890');

 const trocaEmail = (event) => {
 let valorDigitado = event.target.value;
 setEmail(valorDigitado);
 };

 return (
 <div>
 <h2>Faça seu Login:</h2>
 Email: <input type="email" name="email" value={email} onChange={trocaEmail} /><br />
 Senha: <input type="password" name="senha" value={senha} onChange={(event) => setSenha(event.target.value)} />

 <div>
 <p>Email: {email}</p>
 <p>Senha: {senha}</p>
 </div>
 </div>
 );
}

export default FormularioClasse1;

No código acima fizemos o uso do useState para controlar o estado do nosso formulário. Além disso fizemos o uso do atributo onChange para chamar uma função (trocaEmail), e e uma função anônima para alterar o valor de um estado de forma direta (setSenha).

Vamos criar agora um novo componente chamado de FormularioEstado2, cujo o objetivo é demonstrar para você o uso dos diferentes tipos de formulários.

FormularioEstado2 > index.jsx:

import React, { useState } from 'react';

function FormularioEstado2() {
 const [inputValue, setInputValue] = useState('');
 const [selectValue, setSelectValue] = useState('');
 const [textareaValue, setTextareaValue] = useState('');
 const [file, setFile] = useState(null);
 const [checkboxValue, setCheckboxValue] = useState(false);
 const [dateValue, setDateValue] = useState('');
 const [rangeValue, setRangeValue] = useState(0);

 const handleInputChange = (event) => {
 setInputValue(event.target.value);
 };

 const handleSelectChange = (event) => {
 setSelectValue(event.target.value);
 };

 const handleTextareaChange = (event) => {
 setTextareaValue(event.target.value);
 };

 const handleFileChange = (event) => {
 setFile(event.target.files[0]);
 };

 const handleCheckboxChange = (event) => {
 setCheckboxValue(event.target.checked);
 };

 const handleDateChange = (event) => {
 setDateValue(event.target.value);
 };

 const handleRangeChange = (event) => {
 setRangeValue(event.target.value);
 };

 const handleSubmit = (event) => {
 event.preventDefault();
 // Aqui você pode fazer o que quiser com os valores do formulário
 console.log('Valores do formulário:', {
 inputValue,
 selectValue,
 textareaValue,
 file,
 checkboxValue,
 dateValue,
 rangeValue
 });
 };

 return (
 <form onSubmit={handleSubmit}>
 <label>
 Input:
 <input
 type="text"
 value={inputValue}
 onChange={handleInputChange}
 />
 </label>
 <br /><br />
 <label>
 Select:
 <select
 value={selectValue}
 onChange={handleSelectChange}
 >
 <option value="opcao1">Opção 1</option>
 <option value="opcao2">Opção 2</option>
 <option value="opcao3">Opção 3</option>
 </select>
 </label>
 <br /><br />
 <label>
 Textarea:
 <textarea
 value={textareaValue}
 onChange={handleTextareaChange}
 />
 </label>
 <br /><br />
 <label>
 Upload:
 <input
 type="file"
 onChange={handleFileChange}
 />
 </label>
 <br /><br />
 <label>
 Checkbox:
 <input
 type="checkbox"
 checked={checkboxValue}
 onChange={handleCheckboxChange}
 />
 </label>
 <br /><br />
 <label>
 Data:
 <input
 type="date"
 value={dateValue}
 onChange={handleDateChange}
 />
 </label>
 <br /><br />
 <label>
 Range:
 <input
 type="range"
 value={rangeValue}
 onChange={handleRangeChange}
 min="0"
 max="100"
 />
 </label>
 <br /><br />
 <button type="submit">Enviar</button>
 </form>
 );
}

export default FormularioEstado2;

Veja como ficou o resultado:

Por fim, seguindo a mesma estrutura que fizemos nos componentes de classe, vamos criar um novo componente chamado de FormularioEstado3, cujo o objetivo é receber uma imagem por meio de um campo de upload e atualizar no elemento <img>.

FormularioEstado3 > index.jsx:

import React, { useState } from 'react';

function FormularioEstado3() {
 const [imagem, setImagem] = useState(null);

 const handleImagemChange = (event) => {
 setImagem(URL.createObjectURL(event.target.files[0]));
 };

 return (
 <div>
 <input
 type="file"
 onChange={handleImagemChange}
 />
 {imagem && (
 <img src={imagem} alt="Imagem do usuário" />
 )}
 </div>
 );
}

export default FormularioEstado3;

Veja como ficou o resultado final:

Legal, agora que já estamos craques na manipulação de formulários usando componentes de classe e componente de estado, enfim chegou a hora de conhecermos o React Hook Forms 😁

O que é o React Hook Forms?

O React Hook Forms é uma das bibliotecas mais utilizadas para gerenciar formulários em ReactJS de forma eficiente e simples, utilizando hooks.

Seu intuito é de fornecer uma maneira fácil de criar e validar formulários, além de lidar com o estado dos campos de forma eficiente.

Se você está comigo nesta jornada desde o início, talvez tenha percebido que eu já recomendei o uso dessa biblioteca em um caso bem específico. Vou refrescar sua mente... o conteúdo está aqui!

Lá eu mencionei a utilização da biblioteca React Hook Forms para melhorar a otimização dos seus componentes de estado.

E sim, essa biblioteca é tão incrível que conseguimos alterar o estado dos nossos elementos sem a necessidade de gerar renderizações desnecessárias. É como usar um useRef com poderes de um useState 🤩

Dito isso, vamos instalar essa biblioteca no nosso projeto!

Instalando o React Hook Forms

De acordo com a documentação oficial da biblioteca, você vai precisar abrir o prompt de comando dentro da pasta raiz do seu projeto em ReactJS, e executar o seguinte comando de instalação:

npm install react-hook-form

Isso irá instalar a biblioteca dentro do seu projeto.

Usando o React Hook Forms

Para usar o React Hook Forms dentro dos seus componentes, siga os passos a seguir:

Passo 1) Importe a biblioteca para dentro do seu componente:

import { useForm } from 'react-hook-form';

Passo 2) Declare os registradores do formulário:

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

useForm(): Este é um hook do React Hook Forms que inicializa o formulário. Quando chamado, ele retorna um objeto contendo várias funções e estados relacionados ao formulário.

const { ... }: Isso é a desestruturação do objeto retornado pelo useForm(). Em JavaScript, a desestruturação é uma maneira de extrair valores de objetos e arrays para variáveis individuais.

register: Esta é uma função fornecida pelo React Hook Forms que permite registrar campos de entrada no formulário. Ela aceita o nome do campo como argumento e retorna um objeto contendo várias propriedades e métodos para esse campo.

handleSubmit: Esta é uma função fornecida pelo React Hook Forms que lida com a submissão do formulário. Você passa essa função para o atributo onSubmit do elemento <form>. Quando o formulário é submetido, essa função é executada, invocando a função de callback que você passou para ela.

formState: Este é um objeto que contém o estado atual do formulário. Ele inclui propriedades como isDirty, isSubmitting, touched, entre outras. No caso específico, estamos acessando a propriedade errors, que contém os erros de validação dos campos do formulário.

errors: Esta é a propriedade do objeto formState. Ela contém um objeto onde as chaves são os nomes dos campos do formulário e os valores são as mensagens de erro associadas a esses campos. Se não houver erros de validação, este objeto estará vazio.

Resumindo, este comando está inicializando um formulário usando o React Hook Forms e extraindo várias funções e estados úteis desse formulário, como register, handleSubmit e errors, que serão usados para gerenciar os campos do formulário e lidar com sua submissão e validação.

Passo 3) Conecte as variáveis que foram inicializadas com os elementos do seu formulário:

const onSubmit = (data) => {
 console.log(data);
 };

return (
 <form onSubmit={handleSubmit(onSubmit)}>
 <input {...register("nome")} />
 {errors.nome && <span>{errors.nome.message}</span>}

 <input {...register("email", { required: "Email obrigatório" })} />
 {errors.email && <span>{errors.email.message}</span>}

 <button type="submit">Enviar</button>
 </form>
);

Note que o atributo onSubmit está chamando a função handleSubmit de modo a passar como parâmetro uma a função que declaramos dentro do corpo do nosso componente (onSubmit).

Dentro de cada um dos inputs, nós abrimos uma um código Javascript de modo a passar o comando:

{...register("nome")}

O que automaticamente já conta com todos os atributos e funcionalidades que precisamos para controlar o estado desse input. Você se lembra que logo acima eu falei que o useForm() já conta com diversas funções e atributos inclusos?

Então, essa é a facilidade do seu uso 🙂

Lembre-se também de definir um nome para o input, e passá-lo em formato de string para dentro da função register.

Além disso, note que abrimos um outro comando Javascript responsável por retornar os erros relacionados com aquele input:

{errors.nome && <span>{errors.nome.message}</span>}

A chave nome só existe dentro do objeto errors, pois cadastramos ela quando executamos o register("nome").

Dentro da função register, também é possível definir o segundo parâmetro como forma de inserir atributos HTML, como é o caso do required:

<input {...register("email", { required: "Email obrigatório" })} />

Existem diferentes tipos de funcionalidades que podem ser passados como segundo parâmetro da função register, veremos o funcionamento delas mais tarde 😄

Visualizando o estado com o watch

No React Hook Forms existe uma forma de mostrar o valor existente dentro do estado em um JSX, para isso você precisa usar o comando watch().

watch('email')

Para usá-lo você precisa definí-lo dentro do useForm da seguinte forma:

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

Este comando deve existir dentro do return do seu componente, por exemplo:

<div>
 <p>Email: {watch('email')}</p>
 <p>Senha: {watch('senha')}</p>
</div>

Lembrando que o estado email e senha, já deverão ter passado pelo comando register(), por exemplo:

Email: <input type="email" {...register("email")} /><br />
Senha: <input type="password" {...register("senha")} />

2 Exemplos do uso do React Hook Forms

Seguindo a mesma estrutura que vimos durante os tópicos de manipulação de formulários, onde eu criei 3 tipos de componentes diferentes (FormularioEstado1, FormularioEstado2 e FormularioEstado3).

Que tal refazermos todos esses componentes, só que dessa vez usando a biblioteca do React Hook Forms?

Para isso vamos criar três tipos de componentes diferentes:

  • FormularioForms1
  • FormularioForms2
  • FormularioForms3

FormularioForms1 > index.jsx:

import React from 'react';
import { useForm } from 'react-hook-form';

const FormularioForms1 = () => {
 const { register, handleSubmit, watch, formState: { errors } } = useForm();

 const onSubmit = (data) => {
 console.log(data);
 }

 return (
 <div>
 <h2>Faça seu Login (React Hook Forms):</h2>
 <form onSubmit={handleSubmit(onSubmit)}>
 Email: <input type="email" {...register("email")} /><br />
 {errors.email && <span>{errors.email.message}</span>}
 Senha: <input type="password" {...register("senha")} />
 {errors.senha && <span>{errors.senha.message}</span>}

 <div>
 <p>Email: {watch('email')}</p>
 <p>Senha: {watch('senha')}</p>
 </div>

 <input type="submit" />
 </form>
 </div>
 );
}

export default FormularioForms1;

 Veja como ficou o resultado final:

"Ok, mas e se eu quiser que já exista um valor pré-determinado dentro de email e senha?"

Nesse caso, você pode fazer o uso da chave defaultValues dentro do useForm():

import React from 'react';
import { useForm } from 'react-hook-form';

const FormularioForms1 = () => {
 const { register, handleSubmit, watch, formState: { errors } } = useForm({
 defaultValues: {
 email: 'meu@email.com',
 senha: '1234567890'
 }
 });
 
 const onSubmit = (data) => {
 console.log(data);
 }

 return (
 <div>
 <h2>Faça seu Login (React Hook Forms):</h2>
 <form onSubmit={handleSubmit(onSubmit)}>
 Email: <input type="email" {...register("email")} /><br />
 {errors.email && <span>{errors.email.message}</span>}
 Senha: <input type="password" {...register("senha")} />
 {errors.senha && <span>{errors.senha.message}</span>}

 <div>
 <p>Email: {watch('email')}</p>
 <p>Senha: {watch('senha')}</p>
 </div>

 <input type="submit" />
 </form>
 </div>
 );
}

export default FormularioForms1;

Veja como ficou o resultado final:

FormularioForms2 index.jsx:  

import React from 'react';
import { useForm } from 'react-hook-form';

const FormularioForms2 = () => {
 const { register, handleSubmit, watch } = useForm();

 const onSubmit = (data) => {
 console.log('Valores do formulário:', data);
 };

 return (
 <form onSubmit={handleSubmit(onSubmit)}><br />
 <label>
 Input:
 <input type="text" {...register('inputValue')} />
 </label>
 <br /><br />
 <label>
 Select:
 <select {...register('selectValue')}>
 <option value="opcao1">Opção 1</option>
 <option value="opcao2">Opção 2</option>
 <option value="opcao3">Opção 3</option>
 </select>
 </label>
 <br /><br />
 <label>
 Textarea:
 <textarea {...register('textareaValue')} />
 </label>
 <br /><br />
 <label>
 Upload:
 <input type="file" {...register('file')} />
 </label>
 <br /><br />
 <label>
 Checkbox:
 <input type="checkbox" {...register('checkboxValue')} />
 </label>
 <br /><br />
 <label>
 Data:
 <input type="date" {...register('dateValue')} />
 </label>
 <br /><br />
 <label>
 Range:
 <input type="range" {...register('rangeValue')} min="0" max="100" />
 </label>
 <br /><br />
 <button type="submit">Enviar</button><br /><br />
 </form>
 );
}

export default FormularioForms2;

Veja como ficou o resultado final:

Lembrando que você pode definir valores padrão por meio do defaultValues.

No caso do FormularioForms3 que representa o FormularioEstado3, o uso do React Hook Forms não se faz necessário, pois iria adicionar uma camada de complexidade maior ao que já tinhamos feito usando apenas estados.

Explorando o segundo argumento do comando register

O segundo parâmetro do register é um objeto de opções que permite personalizar o comportamento do campo de entrada. Veremos abaixo os diferentes tipos de personalização.

required

Esta opção indica se o campo é obrigatório ou não. Se definido como true, o campo é marcado como obrigatório.   

Além disso, você também pode fornecer uma mensagem de erro personalizada, como no exemplo: { required: "Email obrigatório" }.

Se o campo estiver vazio quando o formulário for submetido, a mensagem de erro será exibida.

<input {...register("email", { required: "Email obrigatório" })} />

maxLength

Define o número máximo de caracteres permitidos para o campo de entrada. Se o usuário tentar digitar mais caracteres do que o especificado, a entrada será invalidada e uma mensagem de erro será exibida.

<input {...register("descricao", { maxLength: 100 })} />

minLength

Define o número minímo de caracteres permitos para o campo de entrada. Se o usuário tentar digitar um valor abaixo dos caracteres especificados, a entrada será invalidada e uma mensagem de erro será exibida.

<input {...register("descricao", { minLength: 5 })} />

pattern

Permite definir uma expressão regular para validar o valor do campo de entrada. Se o valor inserido pelo usuário não corresponder ao padrão especificado, o campo será considerado inválido e uma mensagem de erro será exibida.

<input {...register("email", { pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ })} />

min e max

Essas opções permitem definir valores mínimos e máximos permitidos para campos numéricos ou o comprimento mínimo de uma string.

<input {...register("idade", { min: 18, max: 99 })} />

validate

Permite definir uma função de validação personalizada para o campo. Você pode usar essa função para implementar lógica de validação complexa que não pode ser alcançada com as opções mencionadas anteriormente.

<input {...register("senha", { validate: value => /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/.test(value) || "Senha deve conter pelo menos 8 caracteres, incluindo letras e números" })} />

Além de aceitar uma função de validação, a opção validate também pode aceitar um objeto com múltiplas funções de validação, onde cada chave é um nome para a validação e o valor é a função de validação correspondente.

<input {...register("senha", { 
 validate: {
 tamanho: value => value.length >= 8 || "Senha deve ter pelo menos 8 caracteres",
 maiuscula: value => /[A-Z]/.test(value) || "Senha deve conter pelo menos uma letra maiúscula",
 numero: value => /\d/.test(value) || "Senha deve conter pelo menos um número"
 }
})} />

Os comandos em portugues são mensagens de erros que serão exibidas sempre quando o usuário digitar as especificações incorretas.

valueAsNumber e valueAsData

Essas opções são usadas para controlar como os valores de entrada são tratados. valueAsNumber é usado para interpretar o valor de entrada como um número, enquanto valueAsDate é usado para interpretar o valor como uma data.

<input {...register("idade", { valueAsNumber: true })} />
<input type="date" {...register("dataNascimento", { valueAsDate: true })} />

setValue, trigger e clearErrors

setValue: Este comando é usado para atualizar o valor de um campo de entrada no formulário. Por exemplo, você pode usá-lo para preencher dinamicamente campos do formulário com valores calculados ou vindos de outras fontes de dados. Além disso, ele pode ser útil para inicializar valores de campos controlados.

setValue('fieldName', 'value');

trigger: Este comando é usado para acionar a validação de um campo específico ou de todos os campos do formulário. Pode ser útil quando você deseja validar campos antes de enviar o formulário ou quando deseja atualizar as mensagens de erro exibidas.

trigger(); // valida todos os campos
trigger('fieldName'); // valida um campo específico

clearErrors: Este comando é usado para limpar os erros associados a um campo específico ou a todos os campos do formulário. Isso é útil quando você deseja limpar mensagens de erro depois que o usuário corrigiu um problema ou quando deseja redefinir o estado de erro do formulário.

clearErrors(); // limpa todos os erros
clearErrors('fieldName'); // limpa os erros de um campo específico

Esses comandos oferecem um controle granular sobre o estado e a validação do formulário, permitindo uma experiência de usuário mais dinâmica e responsiva.

Para mais comandos como esses não deixe de acessar a documentação do React Hook Forms 🙂

Posso usar o React Hook Forms em conjunto com useState e useRef?

Sim, e em alguns momentos essa escolha será totalmente necessária, visto que o React Hook Forms deve ser usado especialmente em formulários.

E haverão casos em que você precisa declarar um estado que não estará conectado a nenhum formulário, mas que sofre inflûencia direta dele.

Por exemplo, vamos supor que você tenha um formulário onde os usuários podem inserir dados, mas também queiram incluir um botão de "limpar" para resetar os campos.

Nesse cenário, você pode utilizar o useState para gerenciar o estado dos campos do formulário e o useRef para referenciar os elementos do formulário, enquanto o React Hook Forms cuida da validação e do envio dos dados.

Veja um exemplo de como você pode combinar esses hooks:

import React, { useState, useRef } from 'react';
import { useForm } from 'react-hook-form';

function Formulario() {
 const { register, handleSubmit, reset } = useForm();
 const [formData, setFormData] = useState({});
 const inputRef = useRef();

 const onSubmit = (data) => {
 // Aqui você pode fazer o que quiser com os dados do formulário
 console.log(data);
 };

 const handleLimpar = () => {
 // Limpa os campos do formulário
 reset();
 // Limpa também o estado local
 setFormData({});
 // Foca no primeiro campo após limpar
 inputRef.current.focus();
 };

 return (
 <form onSubmit={handleSubmit(onSubmit)}>
 <input
 type="text"
 name="nome"
 ref={(e) => {
 register(e); // Registrar o campo com o React Hook Forms
 inputRef.current = e; // Associar a referência do input ao useRef
 }}
 onChange={(e) => setFormData({ ...formData, nome: e.target.value })}
 />
 {/* Outros inputs aqui */}
 <button type="submit">Enviar</button>
 <button type="button" onClick={handleLimpar}>Limpar</button>
 </form>
 );
}

export default Formulario;

Preciso abandonar o useState, useRef... e só passar a usar o React Hook Forms?

Não necessariamente, isto é, caso a sua aplicação não faça o uso de muitos formulários.

Maaaaaas.... se você é um tipo de pessoa que se importa muito com a performance das suas aplicações, você pode sim abandonar o uso de useState, useRef, e outros hooks relacionados ao gerenciamento de estado e referências em favor do React Hook Forms em cada um dos seus formulários.

O React Hook Forms é uma biblioteca poderosa e abrangente para lidar com formulários em ReactJS, e pode substituir tranquilamente muitos dos hooks de gerenciamento de estado e referências que você pode estar usando atualmente.

Vantagens de se usar o React Hook Forms

Abaixo listamos as 5 vantagens principais de se utilizar o React Hook Forms no seu projeto.

Simplicidade: Com o React Hook Forms, você pode gerenciar todo o estado do seu formulário em um só lugar, tornando o código mais limpo e fácil de entender.

Validação de formulário integrada: O React Hook Forms oferece uma maneira simples de validar os campos do seu formulário, permitindo que você defina regras de validação diretamente no momento de registrar os campos.

Mensagens de erro automáticas: A biblioteca também lida automaticamente com a exibição de mensagens de erro associadas a campos que falharam na validação.

Mínima re-renderização: O React Hook Forms foi projetado para minimizar a re-renderização desnecessária, o que pode resultar em melhor desempenho para seus componentes de formulário.

Múltiplas estratégias de renderização: Ele suporta várias estratégias de renderização, como renderização condicional, renderização lenta e renderização controlada, para atender às necessidades específicas do seu aplicativo.

Quando usar o React Hook Forms e quando ele não é necessário?

Devemos fazer o uso do React Hook Forms quando:

Termos Formulários Complexos: O React Hook Forms é particularmente útil quando você está lidando com formulários complexos com muitos campos e validações.

Precisamos Melhorar a Performance: Ele é eficiente em termos de desempenho, especialmente para formulários grandes, porque utiliza o conceito de renderização condicional para renderizar apenas os campos que são necessários.

Queremos fazer uma Validação de Formulário: Ele fornece um sistema de validação integrado que permite validar facilmente os campos do formulário com regras personalizadas.

Queremos Executar uma Gestão de Estado Simplificada: O React Hook Forms gerencia o estado dos campos do formulário de forma eficaz, reduzindo a necessidade de escrever muito código manualmente para lidar com mudanças nos valores dos campos.

Quando Temos uma Integração com Hooks do React: Ele se integra bem com outros hooks do React, como useState e useEffect, facilitando a criação de formulários dinâmicos e interativos.

O uso do React Hook Forms não é necessário quando:

Temos Formulários Simples: Para formulários simples com poucos campos e sem muita lógica de validação, pode ser mais simples e direto usar o estado local do React para gerenciar os valores dos campos.

Possuimos Formulários Estáticos: Se você tiver um formulário estático que não precisa de interatividade ou validação, pode ser excessivo usar o React Hook Forms.

Se você já estiver usando uma biblioteca de componentes que inclui funcionalidades de formulário, como Material-UI ou Formik, pode não ser necessário adicionar o React Hook Forms ao seu projeto, uma vez que elas já contam com seus próprios meios de gerenciamento de estados.

Trabalhando com Scheme Validations

Dentro do React Hook Forms nós podemos fazer a integração de uma outra biblioteca chamada de Scheme Validations.

Ela tem por objetivo te ajudar na validação de formulários. Você pode encontrar mais informações na documentação da plataforma.

Instalando o Scheme Validations

Para instalar o Scheme Validations, vá na pasta principal do seu projeto em ReactJS, abra o prompt de comando e execute:

npm install zod

Em seguida precisamos integrar o resolver,dentro do nosso projeto, que nada mais é do que o formato com que iremos integrar isso com a biblioteca de validação:

npm install @hookform/resolvers

Após isso importe a biblioteca necessária para dentro do seu componente:

import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';

Já fora do corpo do seu componente, declare a variável que vai conter todas as configurações do zod:

const schema = z.object({
 name: z.string().nonempty("O campo nome é obrigatório"),
 email: z.string().email("O campo email é inválido").nonempty("O campo email é obrigatório"),
 boolean: z.boolean(),
 numero: z.number(),
 username: z.string().nonempty("O campo username é obrigatório").min(3, "O username tem que ter 3 caracteres no mínimo").max(10, "O username tem que ter 10 caracteres no máximo"),
 telefone: z.string().refine(value => /^\d{2}\d{5}\d{4}$/.test(value), {	message: "O campo telefone é inválido" }),
});

Se você participou da minha Jornada Javascript, vai conseguir compreender o que cada função que está sendo chamada está fazendo dentro do schema.

z.object({}): Isso define um objeto que deve seguir um determinado esquema. No caso, é um objeto com várias propriedades, cada uma com suas próprias validações.

z.string(): Define que o valor da propriedade deve ser uma string.

z.string().nonempty("O campo nome é obrigatório"): Adiciona uma validação à propriedade "name", exigindo que seja uma string não vazia. A mensagem fornecida será retornada se a validação falhar.

z.string().email("O campo email é inválido"): Adiciona uma validação à propriedade "email", exigindo que seja uma string que siga o formato de um endereço de e-mail válido. A mensagem fornecida será retornada se a validação falhar.

z.boolean(): Define que o valor da propriedade deve ser um booleano.

z.number(): Define que o valor da propriedade deve ser um número.

z.string().nonempty("O campo username é obrigatório").min(3, "O username tem que ter 3 caracteres no mínimo").max(10, "O username tem que ter 10 caracteres no máximo"): Adiciona várias validações à propriedade "username". Primeiro, exige que seja uma string não vazia, depois que tenha no mínimo 3 caracteres e no máximo 10 caracteres. As mensagens fornecidas serão retornadas se as validações falharem.

z.string().refine(value => /^\d{2}\d{5}\d{4}$/.test(value), { message: "O campo telefone é inválido" }): Adiciona uma validação à propriedade "telefone" usando uma função de refinamento personalizada. A expressão regular testa se o valor do telefone corresponde ao formato especificado. A mensagem fornecida será retornada se a validação falhar.

Observação: lembre-se que nem todo schema pode conter um nonempty como é o caso de boolean() e number().

Integrando o Scheme Validation com o React Hook Forms

Para que o useForm utilize o Zod, precisamos fazer algumas alterações para que ele siga as regras com base em nosso formulário:

const { register, handleSubmit, formState: { errors }} = useForm({
 resolver: zodResolver(schema),
});

No caso dos erros, eles passarão a existir dentro do objeto formState.errors, onde retorna o erro de cada um dos campos, bastando apenas mostrar abaixo dos inputs:

return (
 <form onSubmit={handleSubmit(onSubmit)}>
 <input
 type="text"
 {...register('name')}
 placeholder="Digite seu nome..."
 />
 {errors.name && <p>{errors.name.message}</p>}
 <button type="submit">Submit</button>
 </form>
 );

Veja um exemplo de código que faz a implementação do React Hook Forms em conjunto com Scheme Validations:

import React, { useEffect } from 'react';
import { useForm } from 'react-hook-form';

import { boolean, z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';

const schema = z.object({
 name: z.string().nonempty("O campo nome é obrigatório"),
 email: z.string().email("O campo email é inválido").nonempty("O campo email é obrigatório"),
 boolean: z.boolean(),
 numero: z.number(),
 username: z.string().nonempty("O campo username é obrigatório").min(3, "O username tem que ter 3 caracteres no mínimo").max(10, "O username tem que ter 10 caracteres no máximo"),
});

function App() {
 const { register, handleSubmit, formState: { errors }} = useForm({
 resolver: zodResolver(schema),
 });

 useEffect(() => {
 // Setar um valor base após o registro do campo
 register('name');
 }, [register]);

 const onSubmit = (data) => {
 console.log(data);
 };

 return (
 <form onSubmit={handleSubmit(onSubmit)}>
 <input
 type="text"
 {...register('name')}
 placeholder="Digite seu nome..."
 />
 {errors.name && <p>{errors.name.message}</p>}
 <button type="submit">Submit</button>
 </form>
 );
}

export default App;

Incrível não? 😄

Arquivos da lição

Os arquivos dessa lição podem ser encontrados neste repositório do GitHub.

Conclusão

Nesta lição você aprendeu a manipular formulários usando componentes de estado e de classe, e ainda viu sobre o funcionamento da biblioteca React Hook Forms.

Até a próxima lição 😎