Seguindo com a terceira parte do projeto que construí na formação React da Rocketseat, um projeto de duas páginas/telas, onde uma tela contém o timer, e a outra tela contém o histórico dos ciclos realizados.
Nesta terceira parte do projeto vamos focar no formulário, validação e desempenho.
Caso queira adquirir os cursos da Rocketseat com o meu cupom de desconto Acesse esse link
Links úteis:
Capítulos:
- 1 - Controlled vs Uncontrolled
- 2 - O melhor dos dois mundos com
React Hook Form
- 3 - Usando
React Hook Form
- 4 - Validação com
Zod
- 5 - Validação com o botão habilitado
- 6 - TypeScript no Formulário
- 7 - Resetando o Formulário
1 - Controlled vs Uncontrolled
Neste conteúdo vamos entender como podemos criar bons formulários em React.
Olhando o nosso formulário da página Home
temos o campo onde colocamos o nome da tarefa e outro campo onde informamos por quanto tempo iremos trabalhar nessa tarefa. E, a primeira coisa que precisamos é de validação nesses campos, para que o usuário não faça o submit desse formulário sem preencher esses campos, ou preenchendo com o valor errado.
Outra coisa é o botão que fica ativo somente quando todos os campos estiverem preenchidos. Então, precisamos monitorar de alguma forma se o usuário está preenchendo, se já preencheu esses campos, podemos dizer em tempo real, para só depois habilitar o botão.
Há duas formas de trabalhar com formulários, a forma controlada e a forma não controlada (Controlled / Uncontrolled), e é muito importante entender esses dois termos, porque eles têm muito a ver como o React funciona.
A forma controlada (Controlled) é sobre manter em tempo real o estado, a informação que o usuário insere na nossa aplicação numa variável do componente. Então há todo momento que o usuário estiver digitando no input, o estado é atualizado com a nova informação, assim sempre teremos o valor mais atual do que o usuário digitou.
Estamos falando de formulário, mas isso pode ser usado em qualquer lugar que tenha um input.
Um exemplo fazendo da forma controlada:
Como temos esse valor em tempo real, a gente consegue facilmente ter acesso a esse valor no momento de fazer o submit. E, facilmente, conseguimos refletir visualmente alterações na interface, baseado no valor desses inputs.
Um exemplo disso é quando queremos habilitar o botão apenas quando o valor da tarefa esteja preenchido:
Então Controlled Components
dentro do React, ou seja, qualquer componente que seja monitorado em tempo real o input do usuário, traz muita fluidez para mostrar ou deixar de mostrar algo na interface da aplicação, baseado nesse input do usuário.
O lado negativo do componente controlado
Toda vez que a gente faz uma atualização de estado, a gente provoca uma nova renderização, ou seja, toda vez que o setTask
é chamado por qualquer motivo, o React precisa recalcular todo o conteúdo do componente, do estado que mudou. Isso de recalcular todo o conteúdo do componente, não necessariamente é lento, mas se a interface for muito complexa, com muita informação, pode, sim, virar um gargalo.
Então em alguns casos, lidar com formulários desta maneira controlada, pode ser um problema para a aplicação em questão de desempenho. Na maioria das vezes não vai ser um problema, mas em alguns momentos, vai ser.
A forma Uncontrolled
Como seria então a forma Uncontrolled
?
Desta forma Uncontrolled
a gente acaba perdendo a fluidez.
Quando é indicado cada?
-
Controlled (Monitorado em tempo real)
:- formulários simples com poucos campos;
- interface simples;
- formulário de login, por exemplo;
-
Uncontrolled (Não Monitorado)
:- formulários gigantes com muitos campos;
2 - O melhor dos dois mundos com React Hook Form
Nessa parte vamos utilizar a biblioteca React Hook Form. Ela nos ajuda a trabalhar com Controlled Components
sem perder o desempenho, evitando renderizações desnecessárias.
O React Hook Form
é uma biblioteca popular para gerenciar formulários em React por várias razões importantes:
Performance otimizada
- Minimiza a quantidade de re-renderizações usando um sistema de registro de campos não controlados, o que melhora significativamente a performance comparado com formulários controlados tradicionais.Menos código
- Reduz significativamente a quantidade de código necessário para gerenciar formulários. Você não precisa criar estados para cada campo nem gerenciar suas atualizações manualmente.-
Validação eficiente
- Oferece validação integrada e fácil de implementar, incluindo:- Validação em tempo real
- Validação personalizada
- Integração com bibliotecas como Yup, Zod ou Joi
-
Melhor experiência de desenvolvimento
- Fornece:- Tipagem forte com TypeScript
- DevTools para debugar
- Tratamento de erros intuitivo
-
Gerenciamento de estados do formulário
- Gerencia automaticamente estados como:- touched/untouched
- dirty/pristine
- validação
- submissão
Para fazer a instalação execute o comando em seu terminal:
$ npm i react-hook-form
commit: build: ➕ add react-hook-form lib $ npm i react-hook-form
3 - Usando React Hook Form
No arquivo src/pages/Home/index.tsx
vamos importar a função useForm
:
import { useForm } from 'react-hook-form'
useForm
é um hook
. Por convenção, um hook
começa com use
no nome. Os hooks
são funções que acoplam uma funcionalidade em um componente existente.
useForm()
retorna um objeto, então conseguimos fazemos a desestruturação desse objeto, e as funções principais são register
e handleSubmit
:
export function Home() {
const { register, handleSubmit } = useForm()
}
register
é uma função que vai adicionar um input ao nosso formulário. useForm()
é como se a gente tivesse criado um novo formulário para a aplicação. E a função register
diz quais os campos que vamos ter no nosso formulário.
Para registrar o TaskInput
:
<TaskInput
id="task"
list="task-suggestions"
placeholder="Dê um nome para o seu projeto"
{...register("task")}
/>
A função register
recebe o nome do input
como argumento/valor e retorna vários métodos/funções e propriedades usadas no HTML:
Então com o spread operator {...register("task")}
estamos pegando todos os métodos e as propriedades do register
e passando para o componente TaskInput
.
E o mesmo para o MinutesAmountInput
:
<MinutesAmountInput
type="number"
id="minutesAmount"
placeholder="00"
step={5}
min={5}
max={60}
{...register("minutesAmount", { valueAsNumber: true })}
/>
{ valueAsNumber: true }
é para transformar de string para number.
Usando handleSubmit
Habilitando o botão (usando o watch)
Com a função watch
podemos observar o campo e saber em tempo real o valor dele:
Usando variáveis auxiliares
Variáveis auxiliares são variáveis que não alteram a funcionalidade do código, mas melhoram a legibilidade do código.
O commit desta parte infelizmente eu acabei deixando junto com a parte 5 e a parte 6 😕 recomendo fazer a separação 😅
commit: feat: ✨ add controlled form with react-hook-form
and validation with zod
4 - Validação com Zod
Por padrão, a biblioteca React Hook Form
não traz nenhuma funcionalidade de validação. Ela prefere ser uma biblioteca enxuta, com menos funcionalidades, assim o usuário consegue escolher bibliotecas complementares de sua preferência, como, por exemplo, bibliotecas de validação de formulários baseada em esquema, já que existem ótimas opções como: Yup
, Zod
, Superstruct
e Joi
.
Aqui vamos utilizar a Zod, pois traz mais integração com TypeScript:
$ npm i zod
commit: build: ➕ add validation lib $ npm i zod
E para o React Hook Form
fazer a integração com essas bibliotecas de validação, precisamos instalar o pacote @hookform/resolvers
:
$ npm i @hookform/resolvers
commit: build: ➕ add lib to integrate react-hook-form with other libs $ npm i @hookform/resolvers
5 - Validação com o botão habilitado
Podemos ver na imagem abaixo que o botão fica desabilitado quando o campo está vazio:
Neste caso, não precisaríamos de validação, mas como em muitos formulários não acontecem isso e necessitam informar ao usuário qual informação está incorreta, vamos habilitar o botão e realizar a validação do formulário.
No arquivo src/pages/Home/index.tsx
vamos fazer a importação do Zod:
import { zodResolver } from "@hookform/resolvers/zod";
import * as zod from "zod";
O código * as zod
é uma técnica do ECMAScript Modules
usado para importar tudo do pacote zod, já que ele não possui export default
.
Antes de utilizar o zod, podemos ver no console.log(data)
que adicionamos anteriormente neste mesmo arquivo, o formato do data
como sendo um objeto:
No mesmo arquivo vamos realizar a validação então de um objeto com zod.object()
:
Vamos verificar a validação dos minutos com o Zod, mas para fazer isso, precisamos comentar o código HTML que já faz essa validação:
Agora podemos testar a validação do Zod. Quando clicamos no botão, percebemos que nenhum resultado aparece no console do navegador, ou seja, não está chegando no console.log
pois estamos com erros de validação:
Visualizando os erros de validação do Zod
Para visualizar os erros de validação do Zod, temos que usar o formState
.
Perceba que o console.log(formState.errors)
está fora da função handleCreateNewCycle
:
Assim já conseguimos visualizar a mensagem de erro:
Caso eu queira colocar uma mensagem diferente:
Essas mensagens podem ser mostradas em tela para o usuário, mas nesse projeto, como temos a validação do campo númerico pelo HTML e o botão que só habilita quando há algo no campo de descrição, não iremos precisar mostrar essas mensagens ao usuário.
Assim podemos remover o formState
e colocar o max={60}
devolta.
O commit desta parte infelizmente eu acabei deixando junto com a parte 3 e parte 6 😕 recomendo fazer a separação 😅
commit: feat: ✨ add controlled form with react-hook-form
and validation with zod
6 - TypeScript no Formulário
Agora vamos colocar uma tipagem para o data: any
como mostrado na imagem abaixo:
Podemos já deduzir qual será a tipagem olhando para o nosso formulário, um objeto; com esses dois campos:
Podemos criar a tipagem de forma manual com interface
:
Existe uma propriedade chamada de defaultValues
que podemos passar pra o objeto de configuração dentro do useForm
, que nos permite setar valores iniciais para os campos:
Se tentarmos usar o comando de autocomplete Ctrl
+ Space
, não há sugestões válidas:
Para obtermos o autocomplete
precisamos usar um generic
, como mostrado quando paramos o mouse em cima do useForm
, assim visualizamos os valores padrão <{}, any>
:
Adicionando o generic
obtemos o autocomplete:
Tipagem automática por inferência
Olhando para o código abaixo jé podemos perceber os tipos pela estrutura:
Usando infer
do TypeScript conseguimos extrair a tipagem do zod.object()
e, assim, podemos remover o interface
:
Note que usamos a palara type
, podemos assumir interface
quando criamos o tipo manualmente, e type
quando o tipo vem de forma automática.
Qual o benefício de usar o infer?
Se no futuro precisássemos adicionar um campo novo, por exemplo, chamado de owner
, automaticamente o infer
adicionaria o tipo do campo. Parando o mouse em cima de NewCycleFormData
podemos notar o campo novo adicionado automaticamente:
Isso é o diferencial do Zod e você pode conferir tudo isso na documentação oficial da bilioteca.
O commit da parte 6 infelizmente eu acabei deixando junto com a parte 3 e parte 5 😕 recomendo fazer a separação 😅
feat: ✨ add controlled form with react-hook-form
and validation with zod
7 - Resetando o Formulário
Ao iniciar o timer clicando no botão Começar
, os campos não estão sendo resetados para o valor original. A biblioteca React Hook Form
fornece a função reset
para resetar os campos para os valores originais do defaultValues
: