React: Formulários parte 2
Published: 2020-08-13 01:11:44


Esta é a segunda parte do assunto de formulário sem bibliotecas no react, na primeira escrevi um exemplo de formulário sem biblioteca que funciona muito bem com validação onChange e hook de validação, mas se você leu a postagem anterior até o final deve ter percebido que o exemplo apesar de ser bom, não era tão performático por ter varias renderizações ao digitar algo nos campos.

>[Get in touch with me on Twitter](https://twitter.com/juciano_barbosa)

Primeira parte do assunto: https://dev.to/jucian0/reactjs-formularios-55mo

E nesta postagem irei abordar mais o tema de desempenho, tentarei responder como criar um componente de formulário mais performático, isto é reduzir ao máximo o número de renderizações da tela, mas sem perder a comodidade de validação onChange.

Link do repositório: [https://github.com/Jucian0/react-form-controlled](https://github.com/Jucian0/react-form-controlled)

Se você não leu a postagem anterior sugiro uma leitura rápida para entender como a aplicação funciona.

## Novo componente FormDebounce

No arquivo, App.jsx tem um comentário:

```jsx

import React from 'react';
import FormControlled from './Components/FormControlled'

function App() {
  return (
    <div className="container-fluid">
      <div className="row">
        <div className="col-lg-6 col-md-6">
          {/* vai receber segundo abordagem */}
        </div>
        <div className="col-lg-6 col-md-6">
          <FormControlled />
        </div>
      </div>
    </div>
  );
}

export default App;
```

Esse comentário vai dar lugar ao novo componente de formulário otimizado. Como os componentes são muito parecidos vou fazer uma cópia do componente FormControlled com o nome FormDebounce, e se você já tem uma certa experiência já deve ter matado a charada ao ler o nome do componente, mas tudo bem continue comigo até o final.

Um detalhe importante é alterar a propriedade de value para defaultValue nos Inputs do formulário, como não estou mais usando um formulário controllado é necessário adicionar uma propriedade que da valor inicial para o componente.

```jsx
 <Input
    name="name"
    onChange={e => setInput({ name: e.target.value })}
    label="Name"
    error={errors.name}
    defaultValue={form.name}
 />
```

Você pode ler mais em: [https://reactjs.org/docs/uncontrolled-components.html#default-values](https://reactjs.org/docs/uncontrolled-components.html#default-values)

![](https://cdn-images-1.medium.com/max/2000/1*LLFIKdP2N03zmRz-nDt5IQ.png)

Agora vou mudar o componente App.jsx para receber o novo componente de formulário:

```jsx
function App() {
  return (
    <div className="container-fluid">
      <div className="row">
        <div className="col-lg-6 col-md-6">
          <FormDebounce />
        </div>
        <div className="col-lg-6 col-md-6">
          <FormControlled />
        </div>
      </div>
    </div>
  );
}
```

## Criando a função debounce
> Debounce é o nome dado a uma função de ordem superior

Mas o que é uma função de ordem superior?
> Função de ordem superior é uma função que retorna outra função

Então debounce é uma função que retorna outra função, e essa função retornada executa outra função que é passada por parâmetro para a função debounce com uma espera de tempo que é passado por parâmetro para a função debounce também.

```javascript
    const funcCallBack = ()=>{}//sou executada dentro de "funcA"'
    const time = 3000//determino o tempo de espera que a "funcCallBack" é executada

    const funcA = debounce(
       funcCallBack,
       time 
    )

    funcA()//passo meus parâmetros para "funcCallBack"
```

De forma simples esse é o funcionamento de uma função debounce, agora abaixo veja como fica o código dela:

```javascript
export const debounce = (fn, wait, immediate) => {
    let timeout

    return (...args) => {
        const context = this

        const later = () => {
            timeout = null
            if (!immediate) fn.apply(context, args)
        }

        const callNow = immediate && !timeout
        clearTimeout(timeout)
        timeout = setTimeout(later, wait)

        if (callNow) {
            fn.apply(context, args)
        }
    }
}
```

O motivo de criar essa função é que quero reduzir o número de vezes que o estado do componente é atualizado e dessa forma diminuir as renderizações desnecessárias.

## Adicionando debounce ao componente de Input

Vou usar a função debounce atrelada ao evento onChange do input e adicionar um tempo de espera de 500 milissegundos. Assim garanto que o estado do componente FormDebounce só muda depois de 500 milissegundos que paro de digitar, em uma situação normal o componente seria renderizado a cada caractere que eu digitasse no input, veja como fica o código:

```jsx
import React, { useState, useRef, useEffect, useCallback } from 'react';
import { debounce } from '../Debounce';

const Input = ({ error, label, onChange, ...rest }) => {
   const [touched, setTouched] = useState(false)
   const inputRef = useRef(null)
   const debounceInput = useCallback(debounce(onChange, 500), [debounce])
   const blurInput = useCallback(() => setTouched(true), [setTouched])

   useEffect(() => {
      inputRef.current.addEventListener('input', debounceInput)
      inputRef.current.addEventListener('blur', blurInput)
   }, [blurInput, debounceInput, inputRef])

   return (
      <>
         <label htmlFor={rest.name}>{label}</label>
         <input className="form-control" {...rest} ref={inputRef} />
         <span className="text-danger">{touched && error}</span>
      </>
   );
}
export default Input
```

Abaixo explico cada pare do código.

* Linha 5 — Continuo usando a bordagem de useState para salvar o estado de touched do input.

* Linha 6 — Usando o hook useRef crio uma referência para o input, com ela pego os eventos input e blur e passo as devidas funções de callback**.**

* Linha 7 — Faço uso da função debounce e passo para ela a função onChange recebida das propriedades do componente Input, e também adiciono o valor de 500 milissegundos.

* Linha 8 — Nesta parte crio uma função que executa o setTouched que no que lhe concerne é responsável por atualizar o estado de touched.

* Linhas 10 a 13 — Usando o useEffect e a referência inputRef adiciono as funções responsáveis por cada evento no seu devido lugar. Em dependências adiciono debounceInput, blurInput e inputRef como dependências, como descrito em uma postagem de Dan Abramov sobre useEffect. [https://overreacted.io/a-complete-guide-to-useeffect/](https://overreacted.io/a-complete-guide-to-useeffect/)
> A common mistake is to think functions shouldn’t be dependencies.

* Linhas 15 a 22 — Retorno o template do componente, não houve muitas mudanças nessa parte do código.

Um detalhe importante para ser explicado é o uso do hook useCallback envolvendo às duas funções que estão sendo usadas dentro do useEffect. Bem useCallback memoriza a função que recebe como argumento e devolve sempre a mesma função a menos que uma de suas dependências mude, e como descrito acima na citação de Dan Abramov as funções dentro de um componente mudam sempre que um componente é renderizado, então quando uso o useCallback garanto que a função não seja recriada desde que as dependências de useCallback não mudem.

## Melhorando o hook useValidation

Na versão da etapa anterior o hook useValidation retornava apenas um objeto de erros, vou adicionar uma nova propriedade ao objeto de retorno o isValid, uma propriedade booliana. Além disso, algumas melhorias no uso de useEffect dentro do hook, veja como fica o código com as mudanças:

```javascript
import { useState, useEffect, useCallback } from 'react'
import { ValidationError } from "yup"

const useValidation = (values, schema) => {
   const [errors, setErrors] = useState({})
   const [isValid, setIsValid] = useState(false)

   const validate = useCallback(async () => {
      try {
         await schema.validate(values, { abortEarly: false })
         setErrors({})
         setIsValid(true)
      } catch (e) {
         if (e instanceof ValidationError) {
            const errors = {}
            e.inner.forEach((key) => {
               errors[key.path] = key.message
            })
            setErrors(errors)
            setIsValid(false)
         }
      }
   }, [schema, values])

   useEffect(() => {
      validate()
   }, [validate])

   return { errors, isValid }
}

export default useValidation
```

Algumas explicações das mudanças que fiz no código:

* Linhas 5 e 6 — useState esta criando estados para as propriedades que o hook devolve, isValid e errors.

* Linhas 8 a 24 — Adicionada função validate, esta função deve tentar passar os valores do form pelo crivo das validações que foram escritas na etapa anterior, se ela obtiver sucesso eu altero o estado de erros com um objeto vazio, mas se houver um erro de validação o fluxo entra no bloco catch esse bloco não pega apenas erros oriundos da promise de validação por isso faço uma validação se o erro é uma instância de ValidationError caso a validação seja afirmativo o atualizo o estado de errors. É importante notar que o processo de validação é assíncrono por isso uso um async await** **nessa função. Esta função esta envolta de useCallback isso vai evitar que a mesma seja recriada sem necessidade, somente quando as dependências mudarem a ela é recriada.

* Linhas 25 a 27 — passo a função validate com dependência, como ela muda quando schema ou values mudam a mesma vai continuar sendo executada quando necessário.

## As outras partes

O schema de validação continua inalterado, e componente form apenas pego a nova propriedade isValid do useValidate e adiciono no botão de submit.

```jsx

  <div className="form-group">
     <button 
        type="button" className="btn btn- 
        primary" disabled={!isValid}>
        Submit
     </button>
 </div>
```

## Comparando os formulários

![](https://cdn-images-1.medium.com/max/2052/1*JRHorHsIIfnktZSWzlKnXw.png)

Vou comparar a quantidade de renderizações usando a extensão do react para desenvolvimento que pode ser encontrado para Chrome e Firefox. No navegador vou clicar f12 e selecionar profiler e Start Profiling.

**Resultados para FormDebounce:**

![](https://cdn-images-1.medium.com/max/2052/1*4hsbtKezq9fcKKbACYK8EQ.png)

Resultados para **FormControlled:**

![](https://cdn-images-1.medium.com/max/2052/1*-U7EkfPOcjVLAsMT91AD0w.png)

No primeiro exemplo temos três renderizações de componente, no segundo temos 13 renderizações, é uma diferença bem grande.

Com este exemplo não estou dizendo que essa deve ser a abordagem ideal, pode ser que em diversos casos isso não faça sentido, mas em outros, deve melhorar muito o desempenho da aplicação.

Link do repositório github: [https://github.com/Jucian0/react-form-debounce](https://github.com/Jucian0/react-form-debounce)

## **Funcionando no codesandbox**

https://codesandbox.io/s/github/Jucian0/react-form-debounce/tree/master/?from-embed

Se você chegou até aqui espero que a postagem tenha servido para alguma coisa.

### Uma Alternativa

Se você acredita que ainda precisa de uma biblioteca de formulários não deixem de conferir um projeto que estou desenvolvendo:

Github do projeto [https://github.com/Jucian0/react-data-forms](https://github.com/Jucian0/react-data-forms)

Docs: [https://www.react-data-forms.org/](https://www.useform.org/)