Simplificando seu código React usando closures
Tags: react, closure, webdev, javascript
Published: 2022-07-31 01:51:43

Imagina o seguinte caso: você precisa fazer um formulário que lida com quatro diferentes campos: field1, field2, field3 e field4. Naturalmente, você cria o seu formulário seguindo essa estrutura:

```
const Formulario = () => {
  const [field1, setField1] = useState("");
  const [field2, setField2] = useState("");
  const [field3, setField3] = useState("");
  const [field4, setField4] = useState("");

  return (
    <form onSubmit={...}>
      <input label="Field 1" value={field1} onChange={(e) => setField1(e.target.value)} />
      <input label="Field 2" value={field2} onChange={(e) => setField2(e.target.value)} />
      <input label="Field 3" value={field3} onChange={(e) => setField3(e.target.value)} />
      <input label="Field 4" value={field4} onChange={(e) => setField4(e.target.value)} />
      <button type="submit">Submit</button>
    </form>
  );
};
```

Temos um estado para cada campo, controlamos cada um dos inputs com seu respectivo valor, e colocamos no onChange um callback que muda o valor de acordo com o que o usuário digitou. Até aqui tudo bem, certo? Bom... esse código tem um problema claro à primeira vista: a lógica do callback onChange de todos os inputs está localizada dentro da função render, coisa que dependendo do tamanho do componente e do callback passado, pode dificultar a manutenção no futuro.
Pois bem: você se incomodou com isso e resolveu esse problema, extraindo todas as funções para callbacks no corpo do componente:

```
const Formulario = () => {
  const [field1, setField1] = useState("");
  const [field2, setField2] = useState("");
  const [field3, setField3] = useState("");
  const [field4, setField4] = useState("");

  const handleChangeField1 = (e) => {
    setField1(e.target.value)
  }

  const handleChangeField2 = (e) => {
    setField2(e.target.value)
  }
  
  const handleChangeField3 = (e) => {
    setField3(e.target.value)
  }

  const handleChangeField4 = (e) => {
    setField4(e.target.value)
  }

  return (
    <form onSubmit={...}>
      <input label="Field 1" value={field1} onChange={handleChangeField1} />
      <input label="Field 2" value={field2} onChange={handleChangeField2} />
      <input label="Field 3" value={field3} onChange={handleChangeField3} />
      <input label="Field 4" value={field4} onChange={handleChangeField4} />
      <button type="submit">Submit</button>
    </form>
  );
};
```
Resolvido agora, certo? Bom, melhorou um pouco porém ainda temos um problema: se tivermos 10 campos, precisaremos declarar 10 callbacks? Ora, analisando os quatro callbacks criados, podemos perceber que todos eles fazem tarefas parecidas: receber um evento e setar o valor do estado com `e.target.value`.
E como poderíamos resolver este problema? Afinal, a função onChange está esperando um callback neste exato formato que criamos. Será que existe uma maneira de criar um callback neste formato, que se adapta a cada diferente estado do input? Bom, para isso podemos utilizar uma estrutura no javascript chamada de **closures**.

## O que são closures?

Um **closure** é um conceito que se refere à uma função que é criada dentro de outra função, com acesso ao seu contexto léxico. Para simplificar, confere esse exemplo:

```
const outer = (name) => {
  const inner = () => {
    console.log(name)
  }
  inner()
}

```
Perceba que, dentro da função outer, é declarada uma função inner, sem parâmetros, porém que consegue acessar o parâmetro name da função outer (contexto léxico), e printar o valor na sequência. Após sua criação, a função inner pode ser utilizada normalmente, da forma como foi criada. Por exemplo, caso fosse executado `outer('marcos')`, seria imprimido a string 'marcos' no console.
Closures também podem ser usados como geradores de funções mais simples:

```
const getPropertySetter = (propertyName) => {
  const setProperty = (obj, value) => {
    obj[propertyName] = value 
  }
  return setProperty
}
```
Com esse closure, posso simplificar o processo de modificar uma propriedade em um objeto, por exemplo. Observe que a função externa recebe somente o nome da propriedade, e retorna uma função nova, que recebe um objeto e o valor da propriedade, e faz a atribuição desse valor à propriedade cujo nome foi informado na função mais externa, no objeto recebido. Essa função poderia ser utilizada da seguinte maneira:

```
const setName = getPropertySetter("name");
const setAge = getPropertySetter("age");
const obj = {};
setName(obj, "marcos");
setAge(obj, 22);
console.log(obj);
// output = { name: 'marcos', age: 22 }
```
Perceba que um closure te permite criar funções que geram outras funções, que resolvem problemas de uma maneira mais simples ou mais conveniente.

## De volta ao problema

Agora que você sabe o que é um closure, como podemos utilizar esse conceito para melhorar o código do formulário? Vamos identificar as semelhanças com os exemplos de closure:
- Precisamos passar um callback para o onChange, com um parâmetro que é o evento
- Porém, algo que muda para cada input é a função setter do estado.
- Logo, podemos criar um closure onde a função externa recebe o setter, e retornar então uma função interna com o formato que desejamos, que faz a tarefa comum:

```
const handleChange = (stateSetter) => {
    const setFieldValue = (e) => {
      stateSetter(e.target.value) 
    }
    return setFieldValue
  }
```
E podemos então reutilizar esse closure em todos os nossos inputs:

```
return (
    <form onSubmit={...}>
      <input label="Field 1" value={field1} onChange={handleChange(setField1)} />
      <input label="Field 2" value={field2} onChange={handleChange(setField2)} />
      <input label="Field 3" value={field3} onChange={handleChange(setField3)} />
      <input label="Field 4" value={field4} onChange={handleChange(setField4)} />
      <button type="submit">Submit</button>
    </form>
  );
```
Perceba que agora, temos a lógica separada da renderização, e o número de campos pode aumentar infinitamente, que não precisaremos escrever novos callbacks, somente reutilizar nosso closure. Ao final, nosso componente fica da seguinte maneira:

```
const Formulario = () => {
  const [field1, setField1] = useState("");
  const [field2, setField2] = useState("");
  const [field3, setField3] = useState("");
  const [field4, setField4] = useState("");

  const handleChange = (stateSetter) => {
    const setFieldValue = (e) => {
      stateSetter(e.target.value) 
    }
    return setFieldValue
  }

  return (
    <form onSubmit={...}>
      <input label="Field 1" value={field1} onChange={handleChange(setField1)} />
      <input label="Field 2" value={field2} onChange={handleChange(setField2)} />
      <input label="Field 3" value={field3} onChange={handleChange(setField3)} />
      <input label="Field 4" value={field4} onChange={handleChange(setField4)} />
      <button type="submit">Submit</button>
    </form>
  );
};
```
Pronto! Temos um componente muito mais limpo e escalável.

## Conclusão
Closures são muito importantes no javascript, especialmente no React. Podem te ajudar e muito a deixar seu código mais limpo e simples, e facilitar a reutilização de código. Aqui eu dei somente um exemplo de como poderiam ser utilizados no React, porém eles são aplicáveis a qualquer lógica semelhante ao que mostrei aqui.

E ai, já sabia do que se tratavam os closures? Aprendeu algo novo? Deixa ai nos comentários! Muito obrigado pela leitura, e até o próximo post!