React: validação de formulários com React Hook Form

React: validação de formulários com React Hook Form
Neilton Seguins
Neilton Seguins

Compartilhe

Você já se viu precisando validar e gerenciar cada campo de dados em formulários com React?

Imagine a situação: você está navegando em um projeto e se depara com o desafio de manter formulários organizados e livres de erros. Os formulários são complexos com muitos campos e o seu código pode ficar confuso e difícil de manter.

Essa hipótese te parece familiar?

É aqui que entra o React Hook Form. Essa biblioteca cuida de todo o processo de validação de formulários, permitindo que você crie formulários eficientes e com menos código.

Ela é bastante flexível, com validações baseadas em restrições que aproveitam as marcações do HTML existente.

É super leve e minimiza o número de renderizações na página, melhorando o desempenho da mesma.

Então neste artigo vamos explorar como o React Hook Form faz o gerenciamento e a validação dos campos de nossos formulários.

Você irá conhecer:

  • Diferença de componentes controlados e não controlados;
  • Como o React Hook Form gerencia e valida os campos;
  • Validação de componentes customizados;
  • Validação de componentes de bibliotecas de terceiros.

E muito mais. Aperta o cinto e vamos nessa!

Qual é a diferença entre componentes controlados e não controlados?

Antes de trabalhar com o React Hook Form é crucial entender a diferença entre componentes controlados e não controlados, pois a biblioteca possui diferentes formas de lidar com os dois tipos. Então vamos entender melhor cada um.

Componentes não controlados

Em um componente não controlado, o estado é mantido pelo próprio DOM, não pelo React.

Isso significa que em um formulário, o estado do input não é conhecido por ele a menos que você consiga essa informação usando outros recursos, por exemplo, o useRef().

import React, { useRef } from 'react';

function Formulario() {
  const nomeRef = useRef();
  const emailRef = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    // Acessando os valores dos inputs diretamente dos elementos do formulário
    console.log(nomeRef.current.value);
    console.log(emailRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        ref={nomeRef}
        name="nome"
        type="text"
      />
      <input
        ref={emailRef}
        name="email"
        type="email"
      />
      <button type="submit">Enviar</button>
    </form>
  );
}

Essa abordagem é mais próxima da forma como o HTML funciona normalmente, ou seja, os valores desses componentes são definidos e lidos através de atributos do HTML, como value, checked (para checkboxes), selected (para select), etc.

import './App.css'

function App() {
  const onFormSubmit = (event) => {
    event.preventDefault()
    const form = event.target;
    const inputs = form.elements;
    const values = {};

    for (let i = 0; i < inputs.length; i++) {
      const input = inputs[i];
      if (input.name) {
        values[input.name] = input.value;
      }
    }

    console.log(values);
  }

  return (
    <>
      <form onSubmit={onFormSubmit}>
        <input type="text" name='nome' placeholder='seu nome'/>
        <input type="email" name='email' placeholder='seu email'/>

        <button type="submit">
          enviar
        </button>
      </form>
    </>
  )
}

export default App

Se quiser entender como o hook useRef() funciona, recomendo ver este vídeo do instrutor Vinicius Neves.

Componentes controlados

Já os componentes controlados possuem seu estado totalmente controlado pelo React.

Isso significa que os valores dos campos são armazenados como parte do estado do componente.

Os valores dos campos do formulário são atualizados em resposta às mudanças de estado do React. Você já teve ter visto componentes controlados como esse:


import React, { useState } from "react";

export const Form = () => {
  const [input, setInputValues] = useState({name: "", email:""});

const handleChange = (e) => {
    const { name, value } = e.target;
    setInputValues({ ...inputValues, [name]: value });
};

 function onSubmit(event) {
    event.preventDefault();
    console.log(input)
  }

  return (
    <form onSubmit={onSubmit}>
      <label>
        Name:
        <input
          type="text"
          onChange={(e) => handleChange(e)}
          value={input.name}
        />
      </label>
      <label>
        Email:
        <input
          type="email"
          onChange={(e) => handleChange(e)}
          value={input.email}
        />
      </label>
      <button type="submit">Enviar</button>
    </form>
  );
};

Os componentes controlados oferecem mais controle sobre os dados do formulário, e são mais recomendados ao trabalhar com formulários.

Se quiser ver exemplos de componentes controlados no React, incluindo checkboxes, select, radio buttons e muito mais, recomendo ler o artigo do Jorge Nascimento na plataforma Dev.to.

No entanto, independente de você usar componentes controlados ou não controlados, pode ser que seus formulários possam se tornar difíceis de manter.

Em momentos assim vale a pena conhecer uma maneira de gerenciá-los. E é aí que entra o React Hook Form.

Banner de divulgação da Imersão IA da Alura em colaboração com o Google. Mergulhe em Inteligência artificial com a Alura e o Google. Serão cinco aulas gratuitas para você aprender a usar IA na prática e desenvolver habilidades essenciais para o mercado de trabalho. Inscreva-se gratuitamente agora!

Por dentro do React Hook Form

Essa biblioteca cuida de todo o processo de validação de formulários, permitindo que você crie formulários eficientes, flexíveis e performáticos, já que ela remove as renderizações desnecessárias.

O React Hook Form utiliza uma abordagem não controlada para gerenciar o estado dos campos do formulário, o que significa menos código e melhor desempenho.

Então, por exemplo:

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

function Form() {
  const { register, handleSubmit } = useForm();

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("nome")} placeholder="Nome" />
      <input {...register("email")} placeholder="Email" />
      <button type="submit">Enviar</button>
    </form>
  );
}

Com o React Hook Form, você registra os campos do seu formulário usando a função register, que basicamente associa um campo de entrada HTML a um estado interno da biblioteca.

Isso permite que o React Hook Form gerencie o estado do formulário, sem a necessidade de criar estados separados para cada campo.

Se quiser entender como register funciona, as funções que retorna e as props que ele aceita, recomendo ler a documentação do React Hook Form clicando aqui neste link.

No exemplo anterior, usamos o register para associar os campos de entrada do formulário ao estado interno do React Hook Form.

Isso nos permite acessar os valores dos campos no momento do envio do formulário.

Você deve tá se perguntando: “Como?”

A mágica tá no register. Quando fazemos:


<input {...register("nome")} placeholder="Nome" />

É o mesmo que fazermos:


const { onChange, onBlur, name, ref } = register('nome');

<input 
  onChange={onChange}
  onBlur={onBlur} 
  name={name}
  ref={ref}
  placeholder="Nome"
/>

O register retorna algumas funções importantes. O ref nos permite obtermos os valores desse campo, e por meio de uma lógica interna conseguimos atualizar este valor com o onChange através do React Hook Form.

Componentes customizados

Em algumas situações, pode ser que sua aplicação já possua componentes desenvolvidos por você (inputs, selects / dropdown, radio button, checkbox…) seguindo os padrões de design da sua equipe.

Em casos como esse, faz mais sentido que haja a integração com a biblioteca React Hook Form do que desenvolver componentes novos do zero. Vamos ver como implementar isso, usando de exemplo o seguinte componente <Select>:


import { forwardRef } from 'react'

export interface IFormValues {
  name: string;
  age: number;
}

const Select = forwardRef<
  HTMLSelectElement,
  { label: string } & ReturnType<UseFormRegister<IFormValues>>
>(({ label }, ref) => (
    <div>
      <label>{label}</label>
      <select ref={ref} >
        <option value="20">20</option>
        <option value="30">30</option>
      </select>
    </div>
  )
);

No componente Select, utilizamos forwardRef() para passar a ref diretamente para o select do HTML.

Isso nos permite integrar facilmente o componente com o React Hook Form, mantendo a referência ao campo de idade (age).

Por padrão, os nós do DOM de um componente são privados. No entanto, às vezes é útil expor um nó do DOM ao seu componente pai, por exemplo, para permitir que ele seja focável, ou neste caso para ter acesso aos atributos como value, onchange, name, etc.

O forwardRef permite que seu componente exponha um nó do DOM ao componente pai com uma ref.

Se você quiser aprender mais sobre o forwardRef e seus usos, recomendo ler a documentação do React clicando aqui neste link.

Você também poderia usar o register diretamente dentro de seu componente, desde que aceite ele como uma prop.

Note que a própria biblioteca React Hook Form nos fornece tipos para evitarmos tipar de forma incorreta as props de nossos componentes customizados.


import { Path, UseFormRegister } from "react-hook-form"
import { IFormValues } from './'

type InputProps = {
  label: Path<IFormValues>
  register: UseFormRegister<IFormValues>
  required: boolean
}

const Input = ({ label, register, required }: InputProps) => (
  <>
    <label>{label}</label>
    <input {...register(label, { required })} />
  </>
)

No exemplo anterior, o componente de <Input /> recebe as props label, o register e um required. O register é retornado da biblioteca React Hook Form, enquanto que o required é um objeto contendo as regras de validação que o register pode receber.

Você poderia usar estes componentes da seguinte forma:


import { useForm, SubmitHandler } from "react-hook-form"
import { Input, Select } from './'
import IFormValues from './'

const App = () => {
  const { register, handleSubmit } = useForm<IFormValues>()

  const onSubmit: SubmitHandler<IFormValues> = (data) => {
    alert(JSON.stringify(data))
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Input label="name" register={register} required />
      <Select label="age" {...register("age")} />
      <input type="submit" />
    </form>
  )
}

Você pode estar se perguntando: “Mas e em casos onde usamos componentes de UI de bibliotecas de terceiros para criar a interface da nossa aplicação?”

É uma ótima pergunta! Vamos entender como trabalhamos com esses tipos de componentes.

Integrando com bibliotecas de UI

Quando estamos usando bibliotecas de componentes como MUI e Ant Design, podemos integrar a React Hook Form sem problemas, mesmo quando esses componentes não aceitam diretamente uma ref.

Nesse tipo de cenário o React Hook Form cuida de todo o processo de registro dos nossos componentes:


import { useForm, Controller, SubmitHandler } from "react-hook-form"
import { Input } from "@material-ui/core"

interface IFormInput {
  nome: string
  senha: string
}

const App = () => {
  const { control, handleSubmit } = useForm<IFormInput>({
    defaultValues: {
      nome: "",
      senha: ""
    },
  })

  const onSubmit: SubmitHandler<IFormInput> = (data) => {
    console.log(data)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="nome"
        control={control}
        render={({ field }) => <Input {...field} />}
      />
      <Controller
        name="senha"
        control={control}
        render={({ field }) => <Input {...field} />}
      />
      <button type="submit">Enviar</button>
    </form>
  )
}

Dentro do formulário, temos dois componentes Controller, um para cada campo (nome e senha).

O Controller é usado para integrar campos de entrada de bibliotecas externas ao react-hook-form.

Este componente recebe algumas props, entre elas um name para servir de identificador único para o campo, um render, que renderiza o componente externo que estamos usando e um control, que será responsável por controlar o componente externo.

E é dessa forma que conseguimos integrar a biblioteca React Hook Form com componentes de bibliotecas de UI externas.

O componente Controller pode receber outras props, e se você quiser se aprofundar no uso dele recomendo ler a documentação disponível aqui neste link.

Conclusão

O React Hook Form é uma ferramenta poderosa para simplificar a validação de formulários no React.

Ao adotar uma abordagem não controlada e oferecer suporte a componentes controlados por meio do Controller, a ferramenta permite criar formulários eficientes, com menos código e melhor desempenho.

Se você quer aprofundar seus conhecimentos em formulários no React, sugiro que faça nossa formação de React Formulários.

Lá, você aprenderá a validar formulários usando bibliotecas populares como o Formik, React Hook Form, além de criar esquemas de validação com bibliotecas como yup e zod.

Espero te ver lá! Até a próxima!

Neilton Seguins
Neilton Seguins

Sou graduado como Bacharel em Ciência e Tecnologia e em Engenharia Mecânica. Atuo como Instrutor de Desenvolvedor de Software na Alura e possuo experiência com desenvolvimento usando JavaScript/TypeScript, React js, Next js e Node.js. Amo compartilhar conhecimento, pois acredito que a educação é transformadora e quero mudar a vida de pessoas através da educação assim como consegui mudar de vida. Também amo ouvir e tocar música, ler livros e mangás e assistir séries.

Veja outros artigos sobre Front-end