ReactJS - validação de formulário com React Hook Form

Lidar com diversas telas e formulários enquanto se garante a validação de todos os campos pode se transformar em um trabalho repetitivo e cansativo. Pensando nisso, trouxemos uma visão de como os hooks do react podem auxiliar você na fluidez desse processo.

Jessica Meira

Jessica Meira

September 23, 2021 | leitura de 10 minutos

dev

Na série montada por esta equipe sobre ReactJS, falamos da utilização desse framework e das libs que nos ajudam durante o processo de desenvolvimento. Ainda trazendo um assunto simples (mas muito útil no dia a dia), vamos abordar a criação de formulários e suas validações.

Caso você ainda não esteja familiarizado com o ReactJS, há alguns artigos bem bacanas aqui no blog que vão sanar dúvidas a respeito dessa poderosa ferramenta.

Formulários

Não importa a senioridade do desenvolvedor ou se é apenas um entusiasta no mundo do desenvolvimento. Em algum momento da sua jornada, e falo com tranquilidade, qualquer profissional já passou pela árdua, mas não tão árdua assim, missão de montar um formulário, seja para login, seja para cadastro, seja até mesmo para uma simples newsletter.

Sempre há a necessidade de criar validações antes de processar os dados. Você pode me questionar se o html com javascript puro já não valida essas informações, e a resposta será positiva. Teoricamente, o atributo required pode ser colocado dentro do HTML no campo input e, também, pode-se usar a abordagem de validação em javascript através de métodos, incontáveis métodos. O ponto é: fazer isso em cada campo de um formulário se torna muito maçante e pode-se até dizer inseguro, assim como, talvez, uma tarefa considerada até retrabalho.

Caso você esteja fazendo uma landing-page que contenha apenas um form, essa opção se torna muito válida, não há motivo para complicar, mas estamos levantando a possibilidade do desenvolvimento de um projeto que contenha diversos forms e inputs. Posso afirmar que montar as ações e validações desse elemento acaba sendo uma ação confusa. Mesmo com o react e utilizando-se o useState, teremos um formulário complexo e com mais linhas de código, com uma renderização mais complexa, ainda que estejamos utilizando componentes.

React Hook Form

React Hook Form é uma biblioteca que auxilia na criação e validação dos formulários, como já mencionado, além de reduzir a quantidade de código desenvolvido, fazendo com que a captura de ações do formulário também seja mais objetiva. Outra facilidade que ele traz é na melhora significativa de desempenho, já que a ação de renderização também pode ser controlada. Dessa forma, apenas as alterações de entradas são rerrenderizadas, não o formulário inteiro.

Instalação: 

npm install react-hook-form

ou:

yarn add react-hook-form

Importação e uso:

import { useForm, useController } from "react-hook-form";

useForm é um hook personalizado para gerenciar os formulários. Possui alguns argumentos opcionais e métodos que auxiliam na hora de manipular os dados, por exemplo: register, formState, watch, handleSubmit e reset.

Contextualizando alguns métodos do useForm, todos os métodos e usos podem ser encontrados na documentação, já que não serão abordados aqui.

Register: Responsável por registrar o valor do input e atribuir ao seu hook form.

formState: Salva o estado do input do formulário, muito usado para retornar os erros de validação, por exemplo.

watch: Quando declarado e atribuído para um input, retorna apenas o valor do input esperado.

handleSubmit: É o método responsável por manipular ou lidar com o submit do form.

reset: Responsável por apagar ou resetar o state do hook form.

useController: É um hook que funciona de forma similar a Controller, por meio do qual é possível passar as props diretamente para os campos do formulário, conforme o exemplo da documentação:

const { field: input } = useController({ name: 'test' })

const { field: checkbox } = useController({ name: 'test1' })

<input {...input} />

<input {...checkbox} />

Apresentando agora de forma prática, pode ser escrito um código similar a este:

//importar o userform da lib do react-hook-form

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

function  App() {

  //declarar os métodos que serão utilizados para manipular o form

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

  //declarar para qual função o método handleSubmit irá enviar as informações

  const  loginUser  =  data  =>  console.log(data);

  return (

    //no onSubmit do form o método handleSubmit irá manipular as informações para o loginUser

    //o register recebe o nome do input, e os errors apresentam na tela os erros de validação

    <form  onSubmit={handleSubmit(loginUser)}>

      <input  {...register("name", {required:  true})} />

      {errors.name && <span>Fill the username</span>}

      <input  {...register("password", { required:  true })} />

      {errors.password && <span>Fill the password</span>}

      <input  type="submit" />

      <input  type="button"  onClick={()=>  reset()}  value="Limpar"/>

    </form>

  );

}

export  default  App;

Simples e sem muito código, ainda assim, usando-o dessa maneira, eu não estarei no melhor cenário. Então, para avançar um pouco e aproveitar que estamos lidando com react e temos a possibilidade de ter um reaproveitamento de código, podemos criar alguns componentes para este formulário.

Como há elementos que sempre se repetirão, criaremos um componente, que chamarei de FormInputs. Ele será uma junção de label com input:

const  FormInputs  = ({label, name, type, register}) => {

    return(

      <div>

        <span>{label} </span>

        <input  type={type}  {...register(name, {required:  true})} />

      </div>

    );

}

export  default  FormInputs

Note que, por parâmetro, é passado o texto da própria label como label; nome do input, necessário para o register, que também é passado por parâmetro; e o tipo do campo.

Deixando de uma maneira um pouco mais organizada, também separamos toda a parte de código da primeira tela. Na importação e utilização desse componente, será necessário importar o useForm e o FormInputs que criamos do campo reutilizado, nos atentando ao <FormInputs /> e aos parâmetros mencionados anteriormente. O register, nesse contexto de componente, será passado da seguinte forma: o primeiro register é o nome do atributo e o register posterior é o método que extraímos do useForm.

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

import FormInputs from  "../fields/inputs";

const  Login  = () => {

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

    const  loginUser  =  data  =>  console.log(data);

    return(

    <form  onSubmit={handleSubmit(loginUser)}>

      <FormInputs  label="Email"  name  ="email"  type="email"  register={register}/>

      {errors.firstname && <span>Email required</span>}

      <FormInputs  label="Password"  name  ="password"  type="password"  register={register} />

      {errors.firstname && <span>Password required</span>}

      <input  type="submit" />

      <input  type="button"  onClick={()=>  reset()}  value="Limpar" />

    </form>

    );

}

export  default  Login

Nessa construção, é muito interessante utilizar o styled-components para separar melhor e estilizar o formulário, então podemos seguir com ele. Apenas um spoiler para um dos próximos assuntos.

Componentes do form com Styled components

Uma breve explicação sobre o styled-components. É uma lib que nos permite escrever códigos CSS no JS. O que significa? Que não há mais necessidade de importar o arquivo CSS para dentro das nossas páginas.

Instalação:

npm install styled-components

ou:

yarn add styled-components

O Import para o style-components é o import styled from "styled-components";

Com isso, podemos criar um componente estilizado para cada campo utilizado na aplicação. No exemplo que estamos trabalhando, uma recomendação é criar um para cada elemento a nível de prática do código, ou seja, criar componentes para o Form, Button, Span, Main, Input e Div da mesma maneira sempre, conforme o código:

import styled from  "styled-components";

const  Button  =  styled.button``;

export  default  Button

Toda a parte de estilização é feita dentro da crase. Ela fica por sua conta, e, caso você ainda não conheça o styled-components, é uma boa maneira de ter uma experiência com ele.

Já tendo todos os componentes, podemos criar um outro componente de login

import React from  "react";

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

import Form from  "../form";

import Fields from  "../fields";

import Button from  "../button";

const  NewLogin  = () => {

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

  const  newLogUser  = (login) =>{

    console.log(login);

  };

  return (

    <Form  onSubmit={handleSubmit(newLogUser)}>

      <Fields  label="login"  name="username"  type="text"  register={register} />

      {errors.username  && <span>Email required</span>}

      <Fields  label="senha"  name="password"  type="password"  register={register} />

      {errors.password  && <span>Password required</span>}

      <Button>Enviar</Button>

    </Form>

  );

}

export  default  NewLogin;

Não vamos estudar a fundo o que será feito com os dados porque, neste momento, vamos apenas até a validação.

Validação de formulário

Chegamos à melhor parte, e eu sei que você estava esperando por isso: a validação do formulário. O React Hook Form suporta validação de formulário baseada em schema, como o Yup, por meio do qual você pode passar seu schema para useForm como um parâmetro opcional. Então, o Hook Form validará os dados de entrada e retornará com erros ou com um resultado válido.

E é aqui que a mágica acontece.

Para instalar o yup:

npm install @hookform/resolvers yup

ou:

yarn add @hookform/resolvers yup

É necessário fazer os dois imports:

import { yupResolver } from  "@hookform/resolvers/yup";

import  *  as yup from  "yup";

Como dito anteriormente, yup é uma validação baseada em schema, então criaremos uma const schema e passaremos todos os parâmetros necessários para que ele funcione corretamente. O schema na íntegra pode ser encontrado na documentação, mas o necessário para que as nossas validações ocorram no momento é o seguinte bloco de código:

const  schema  = yup.object().shape({

    username: yup.string().required(),

    password: yup.string().required('Please Enter your password'),

  });

O .object vem com um valor padrão já definido, que "constrói" a forma do objeto e define quaisquer padrões para os campos com o auxílio do .shape (que define as chaves do objeto e os esquemas para essas chaves). As chaves atuam com o atributo name do elemento. Caso no formulário haja mais inputs para se validar, a quantidade de objetos dentro do shape também aumentará.

O componente de formulário, agora inteiro, ficará similar a este:

import React from  "react";

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

import { yupResolver } from  "@hookform/resolvers/yup";

import  *  as yup from  "yup";

import Form from  "../form";

import Fields from  "../fields";

import Button from  "../button";

const  NewUser  = () => {

  const  phoneReg  =  /(\(?\d{2}\)?\s)?(\d{4,5}\-\d{4})/

  const  schema  = yup.object().shape({

    name: yup.string().required(),

    email: yup.string().email('Invalid email format').required(),

    phone: yup.string().matches(phoneReg, 'Phone number is not valid'),

    passwd: yup.string().required(),

  });

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

    resolver:  yupResolver(schema)

  });

  const  newLogUser  = (data) =>{

    console.log(data);

  };

  return (

    <Form  onSubmit={handleSubmit(newLogUser)}>

      <Fields  label="login"  name="login"  type="text"  register={register} />

      <p>{errors.name?.message}</p>

      <Fields  label="email"  name="email"  type="email"  register={register} />

      <p>{errors.email?.message}</p>

      <Fields  label="telefone"  name="phone"  type="tel"  register={register} />

      <p>{errors.phone?.message}</p>

      <Fields  label="senha"  name="passwd"  type="password"  register={register} />

      <p>{errors.passwd?.message}</p>

      <Button>Enviar</Button>

    </Form>

  );

}

export  default  NewUser;

Também incluí outros tipos de validação yup no exemplo:

  • string.required: Esperar algum valor. Assim, strings vazias são consideradas vazias.

  • string.matches: Permite fazer comparações principalmente utilizando regEx.

  • string.email: Valida se é um endereço de email.

Outras duas muito utilizadas, mas que eu não coloquei no exemplo, são:

  • string.min: define o tamanho mínimo da string.

  • string.max: define o tamanho máximo da string

Caso ainda haja a necessidade de fazer validações que saiam do escopo da string do yup, é possível utilizar o método matches, conforme explicado, com algum RegEx. Assim como no exemplo, no qual não há validação alguma do tipo telefone, foi criada a expressão regular para esse campo e o yup fará o match do campo com a expressão passada por parâmetro. 

Conclusão

Este artigo teve o intuito de trazer informação sobre a montagem de um formulário utilizando-se os hooks do react, especificamente o React Hook Form, que tem uma abordagem simplista e direta. Adicionando-se os métodos de validação com o yup, ambos trazem uma maneira mais rápida para lidar com forms, principalmente quando estamos diante de aplicações com inúmeros formulários e telas. Mantê-los padronizados se torna uma garantia de qualidade e desempenho.

Referências
Documentação React-Hook-Form
Documentação Styled-components 
Yup
Expressões regulares - RegEx
Jessica Meira
Jessica Meira

Software Engineer | Cursando pós-graduação em Inteligência Artificial, um pouco curiosa nas horas vagas. Mais de 15 anos na área de Tecnologia e apaixonada por música e instrumentos.

LinkedIn