React

Formik vs React Hook Form

12 de junho de 2021Leitura de 12 min

Introdução

Aqueles que já desenvolveram formulários no React através de Controlled Components sabem o quanto pode ser trabalhoso armazenar os valores dos campos em estados. A adição de máscaras e validações então só aumentam as linhas de código e quantidade de renderizações do formulário.

Para resolver problemas como esse foram surgindo diversas libs de formulários React ao longo dos anos. Nesse artigo serão comparadas as duas mais utilizadas até o momento, Formik e React Hook Form. Como critérios de comparação considerou-se o tamanho de cada uma, a quantidade de downloads, o número de dependências, performance, dentre outros. Ao final será apresentado um exemplo prático com a criação de um formulário contendo nome, idade, email e telefone, aplicando máscaras e validações.


Comparativo Técnico

O comparativo técnico foi desenvolvido com base na análise de dados do npm trends, BundlePhobia, e dos próprios repositórios e sites das libs.

Quantidade de downloads

Abaixo é apresentado um gráfico com a quantidade de downloads das libs por semana no período de um ano.

Quantidade de downloads nos últimos 3 meses

Como se pode notar, com pouco mais de 1.400.000 downloads semanais a Formik apresenta maior popularidade se comparada aos quase 800.000 downloads da React Hook Form. Considerando esse período de um ano, pode-se concluir que ambas possuem um índice bem próximo de crescimento, sendo que um ano atrás a Formik apresentava aproximadamente 865.000 e a React Hook Form 220.000. Na tabela abaixo podemos comparar os dados mais precisamente.

FormikReact Hook Form
Atualmente1.414.310797.395
1 ano atrás864.294220.554
Crescimento550.016576.841

Curiosidade: Eu me perguntei o porquê dessa queda brusca na quantidade de downloads no gráfico no período entre Dezembro e Janeiro. Então aumentei o período para 5 anos, e reparei que o mesmo comportamento se repetia, mais precisamente nas semanas do dia 23 de Dezembro ao dia 1 de Janeiro. Não é coincidência que sejam períodos em que se comemoram o Natal e Ano Novo.

Quantidade de Downloads nos últimos 5 anos

Informações do Github

Por algum motivo o npm trends não estava apresentando os dados referentes a stars, forks, issues e updated. Por isso optei por buscar os dados diretamente em cada repositório.

FormikReact Hook Form
Watch241157
Star27.2k20.7k
Fork2.2k939
Issues5051
Pull requests1009

Novamente aqui a Formik demonstra uma maior popularidade considerando a quantidade de watches, stars e forks. Entretando a React Hook Form não fica para trás, com índices bem satisfatórios. O que chama a atenção são as 505 isues em aberto da primeira contra apenas 1 da segunda. Mesmo sabendo da popularidade da Formik perante a React Hook Form, os índices de Issues são bem discrepantes.

Integração e suporte

Analisando a documentação das libs é possível ver que ambas apresentam suporte e integração com diversas outras linguagens e ferramentes do mundo Front-end.

FormikReact Hook Form
TypescriptSimSim
React NativeSimSim
ReduxSimSim
Material UISimSim
YupSimSim

Tamanho do bundle e composição de dependências

Ao adicionar qualquer dependência em seu projeto, um fator importante a se considerar é o tamanho dela e quais outras dependências serão adicionadas para o seu funcionamento. Isso porque elas se juntarão ao bundle final da sua aplicação, caso ela seja uma dependência para produção. Os dados abaixo são do site BundlePhobia, e apresentam o tamanho do bundle e a composição de dependências de cada lib.

Tamanho do bundle
FormikReact Hook Form
minified44.4k24.9kb
minified + gzipped13.1kb8.5kb
Composição de dependências da Formik

Composição de dependências da Formik

Composição de dependências da React Hook Form

Composição de dependências da React Hook Form

Pode-se concluir que a React Hook Form venceu em ambas as métricas, tanto no tamanho do bundle, apresentando uma diferença de quase 5kb a menos se comparada com a Formik, quanto na composição de módulos, apresentando 0 dependências contra 7 da Formik.


Comparativo de desenvolvimento

A Formik e a React Hook Form seguem diferentes formas de implementação de formulários. O objetivo dessa métrica não é pontuar qual das duas formas é a melhor, já que cada desenvolvedor tem a sua preferência. A ideia aqui é criar um formulário de acordo com uma determinada lista de requisitos, e mostrar como tratá-los utilizando cada uma. Abaixo é apresentado uma tabela com os inputs e suas devidas validações:

CampoTipoRequisitos
Nometextobrigatório
Idadetextobrigatório, maior que 10
Emailemailobrigatório, com formato válido de email
Telefonetelobrigatório, com formato válido de telefone

Desenvolvendo um formulário com a lib Formik

A Formik apresenta duas formas de se desenvolver um formulário, a primeira utilizando os componentes Formik, Form, e Field, e a segunda utilizando o hook useFormik(). Nesse comparativo eu optei pela utilização do hook pela forma como a implementação se assemelha a criação de um formulário padrão no React.

Para começar é necessário instalar a dependência utilizando npm install formik ou yarn add formik. Após isso nós criamos um componente e adicionamos o seguinte trecho de código:

import React from 'react'
import { useFormik, FormikErrors, FormikValues } from 'formik'
import { validateEmail, validatePhone } from 'src/utils/validations'

const FormikExample = () => {
  const inputNames: FormikValues = {
    name: 'Nome',
    age: 'Idade',
    email: 'Email',
    phone: 'Telefone'
  }
  const { handleSubmit, handleChange, values, errors } = useFormik({
    initialValues: {
      name: '',
      age: '',
      email: '',
      phone: ''
    },
    onSubmit: (formValues) => {
      console.log('values', formValues)
    },
    validate: (values: FormikValues) => {
      const errors: FormikErrors<FormikValues> = {}

      Object.keys(values).forEach(key => {
        if (!values[key]) {
          errors[key] = `${inputNames[key]} é um campo obrigatório`
        } else {
          if (key === 'age' && parseInt(values[key]) <= 10) {
            errors[key] = 'A idade deve ser maior que 10'
          }
          if (key === 'email' && !validateEmail(values[key])) {
            errors[key] = 'O email digitado é inválido'
          }
          if (key === 'phone' && !validatePhone(values[key])) {
            errors[key] = 'O número de telefone digitado é inválido'
          }
        }
      })

      return errors
    }
  })

  [...]
}


Ao iniciar o useFormik() nós passamos as seguintes propriedades:

  • initialValues: recebe um objeto contendo o nome e o valor inicial de cada campo do formulário.
  • onSubmit: recebe um método que será chamado quando o formulário for enviado
  • validate: recebe um método responsável por realizar a validação dos campos, e retornar um objeto com os erros.

E com ele podemos obter as seguintes variáveis e métodos:

  • values: objeto com os valores dos campos do formulário
  • errors: objeto com os erros de validação dos campos do formulário
  • handleSubmit: método responsável por tratar o envio do formulário
  • handleChange: método responsável por tratar os eventos de mudanças dos campos

Para fazer o tratamento das máscaras temos que criar um método que será chamado a cada mudança nos campos do formulário. Caso ele receba por parâmetro uma função para formatação de valor, então ele pega esse valor formatado, o altera no ChangeEvent, e o envia para o handleChange disponibilizado pelo useFormik().

import React, { ChangeEvent } from 'react'

const FormikExample = () => {
  [...]

  function onChange (event: ChangeEvent<HTMLInputElement>, mask?: (value: string) => string){
    if (mask) {
      const newEvent = event
      newEvent.target.value = mask(event.target.value)
      handleChange(newEvent)
    } else {
      handleChange(event)
    }
  }

  [...]
}


Por fim nós criamos os campos do formulário passando as funções de formatação para os que precisam de máscara.

import React from 'react'
import { maskPhone, onlyNumber } from 'src/utils/masks'

const FormikExample = () => {
  [...]

  return (
    <form className='form formik' onSubmit={handleSubmit}>
      <header>
        <h3>Formik</h3>
      </header>
      <div>
        <label htmlFor='name'>Nome</label>
        <input
          type='text'
          id='name'
          name='name'
          value={values.name}
          onChange={onChange}
        />
        <span className='error'>{errors.name}</span>
      </div>
      <div>
        <label htmlFor='age'>Idade</label>
        <input
          type='text'
          id='age'
          name='age'
          inputMode='numeric'
          value={values.age}
          onChange={(event: ChangeEvent<HTMLInputElement>) => onChange(event, onlyNumber)}
        />
        <span className='error'>{errors.age}</span>
      </div>
      <div>
        <label htmlFor='email'>Email</label>
        <input
          type='email'
          id='email'
          name='email'
          value={values.email}
          onChange={onChange}
        />
        <span className='error'>{errors.email}</span>
      </div>
      <div>
        <label htmlFor='phone'>Telefone</label>
        <input
          type='tel'
          id='phone'
          name='phone'
          value={values.phone}
          onChange={(event: ChangeEvent<HTMLInputElement>) => onChange(event, maskPhone)}
        />
        <span className='error'>{errors.phone}</span>
      </div>
      <button type='submit'>Enviar</button>
    </form>
  )
}

Para o desenvolvimento do formulário foram necessárias ao todo 117 linhas de código. Você pode ver o código completo nesse link.

Desenvolvendo um formulário com a lib React Hook Form

Da versão 6 para a versão 7 do React Hook Form ocorreram algumas mudanças no registro e no recebimento de erros dos campos. Abaixo vocês podem ver a diferença:

// Versão 6
const { register, errors } = useForm()
<input
  type='text'
  name='name'
  ref={register({
    required: 'Nome é um campo obrigatório'
  })}
/>
errors.name?.message

// Versão 7
const { register, formState: { errors } } = useForm()
<input
  type='text'
  {...register('name', {
    required: 'Nome é um campo obrigatório'
  })}
/>
errors.name?.message

Nesse comparativo utilizaremos a última versão lançada. Você pode instalá-la utilizando npm install react-hook-form ou yarn add react-hook-form.

O React Hook Form disponibiliza um hook chamado useForm(). Para o desenvolvimento do formulário nós vamos criar um componente e fazer da seguinte forma:

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

const ReactHookFormExample = () => {
  const { register, handleSubmit, formState: { errors }, setValue } = useForm()

  [...]
}

  • register: método que faz o registro de todos os campos do formulário
  • handleSubmit: método responsável por tratar o envio do formulário
  • errors: objeto com os erros de validação dos campos do formulário
  • setValue: método que muda os valores de um ou mais campos do formulário

Após isso nós criamos o nosso formulário, fazendo o registro dos campos. O método setValue é utilizado para fazer a aplicação das máscaras.

import React from 'react'
import { useForm } from 'react-hook-form'
import { maskPhone, onlyNumber } from 'src/utils/masks'
import { validateEmail, validatePhone } from 'src/utils/validations'

const ReactHookFormExample = () => {
  const { register, handleSubmit, formState: { errors }, setValue } = useForm()

  return (
    <form className='form react-hook-form' onSubmit={handleSubmit(submitForm)}>
      <header>
        <h3>React Hook Form</h3>
      </header>
      <div>
        <label htmlFor='name'>Nome</label>
        <input
          type='text'
          id='name'
          {...register('name', {
            required: 'Nome é um campo obrigatório'
          })}
        />
        <span className='error'>{errors.name?.message}</span>
      </div>
      <div>
        <label htmlFor='age'>Idade</label>
        <input
          type='text'
          id='age'
          inputMode='numeric'
          {...register('age', {
            required: 'Idade é um campo obrigatório',
            min: {
              value: 11,
              message: 'A idade deve ser maior que 10'
            }
          })}
          onChange={(event: ChangeEvent<HTMLInputElement>) => setValue('age', onlyNumber(event.target.value))}
        />
        <span className='error'>{errors.age?.message}</span>
      </div>
      <div>
        <label htmlFor='email'>Email</label>
        <input
          type='email'
          id='email'
          {...register('email', {
            required: 'Email é um campo obrigatório',
            validate: {
              isEmail: value => validateEmail(value) || 'O email digitado é inválido'
            }
          })}
        />
        <span className='error'>{errors.email?.message}</span>
      </div>
      <div>
        <label htmlFor='phone'>Telefone</label>
        <input
          type='tel'
          id='phone'
          {...register('phone', {
            required: 'Telefone é um campo obrigatório',
            validate: {
              isPhone: value => validatePhone(value) || 'O número de telefone digitado é inválido'
            }
          })}
          onChange={(event: ChangeEvent<HTMLInputElement>) => setValue('phone', maskPhone(event.target.value))}
        />
        <span className='error'>{errors.phone?.message}</span>
      </div>
      <button type='submit'>Enviar</button>
    </form>
  )
}

Como vocês podem observar, as validações são criadas dentro de um objeto passado para o método register(). O React Hook Form já disponibiliza algumas propriedades para validações, como o required para campos obrigatórios, e o min para validação de números mínimos, bem semelhante a forma padrão de validação de formulários no HTML. Para casos mais específicos como validação de email e telefone há a propriedade validate.

O desenvolvimento desse formulário gerou ao todo 95 linhas de código, que você pode visualizar nesse link.


Comparativo de performance

Um ponto importante a se considerar no desenvolvimento de qualquer componente React é a sua performance. E para medi-la nos formulários criados na seção anterior, foram definidas duas métricas principais: a quantidade de renderizações que cada formulário terá enquanto o usuário estiver preenchendo-o, e o tempo gasto na primeira renderização do formulário em tela.

Para saber a quantidade de renderizações de cada componente, eu criei uma variável com valor inicial 0 que incrementava a cada vez que formulário renderizava. Abaixo nós podemos ver o resultado de cada um:

Formik - Quantidade de renderizações

Formik - Quantidade de renderizações

React Hook Form - Quantidade de renderizações

React Hook Form - Quantidade de renderizações

Aqui claramente podemos ver que em termos de quantidade de renderização a React Hook Form se destaca se comparada a Formik, tendo apenas 1 contra 74.

Para calcular o tempo que cada componente leva para ser renderizado na primeira vez, eu utilizei a funcionalidade de Profiler disponibilizada pela extensão React Developer Tools.

Formik - Tempo gasto na primeira renderização

Formik - Tempo gasto na primeira renderização

React Hook Form - Tempo gasto na primeira renderização

React Hook Form - Tempo gasto na primeira renderização

Com uma diferença de 0.7ms, a Fomik se destaca sobre a React Hook Form no tempo gasto na primeira renderização. É importante ressaltar que esses dados podem variar de acordo com o tamanho do formulário desenvolvido.


Resultado

Abaixo você pode ver o resultado do desenvolvimento e comparar a quantidade de renderizações presentes em cada tipo de formulário. Como extra adicionei um formulário desenvolvido utilizando o conceito de Controlled Components.

Link do projeto

Conclusão

Se comparada a forma padrão de desenvolvimento de formulários no React, ambas as libs trazem muitas vantagens. O seu código fica mais enxuto, e você não tem que se preocupar em ficar armazenando os valores em estados. Então no que se refere a parte de desenvolvimento, considero as duas empatadas, por tornarem esse processo mais fácil e por apresentarem uma documentação simples e objetiva.

Já se levarmos em conta o comparativo técnico e de performance, a React Hook Form leva a melhor por ser menor, não conter nenhuma outra dependência externa, e evitar renderizações nas mudanças de valores dos campos, oferecendo mais vantagens do que a Formik.

Referências

© 2021 Thiago Gonçalves Salomé - Todos os direitos reservados