Um rant sobre API mal feita
Tags: react, braziliandevs
Published: 2024-03-09 13:42:47

---
title: Um rant sobre API mal feita
published: true
description: Uma historia sobre como fiquei frustrado com uma decisão mal feita
tags: react, braziliandevs
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2024-03-09 12:41 +0000
---

> Esse post é mais reclamação do que um argumento bem feito. Por favor não leve tão a sério.

Eu trabalhei vários anos com Formik. Não gosto muito dele, já que ele tem um aspecto "boilerplate". Por conta disso, queria aprender como lidar com forms de outras maneiras.

No projeto que estou trabalhando hoje, já temos o `react-hook-form` instalado. Eu não tenho experiência com ele, então resolvi aproveitar a oportunidade.

Resumindo bastante, eu odiei. Fiquei trabalhando até tarde para tentar resolver um form com ele, e no final eu desisti e usei React puro. A API dele é bem complexa, e a documentação não ajuda.

## Um exemplo

Eu estava usando um form simples que tem um único campo de username. Esse campo não aceita espaços, e se o usuário inputar espaço, ele deve ignorar. Além disso, o campo tem algumas validações.

Utilizando React puro, essa solução é possível.

```jsx
function UsernameForm({onSubmit}) {
  const form = useForm()
  const [username, setUsername] = useState('')
  const isValid = validateUsername(username)
  return (
    <form onSubmit={form.handleSubmit(onSubmit)}>
      <input
        type="text"
        name="username"
        value={username}
        onChange={
          e => 
            setUsername(e.target.value.replaceAll(' ', ''))
        }
      />
      <button type="submit" disabled={!isValid}>Salvar</button>
    </form>
  )
}
```

> Observação: nesse exemplo de username, remover espaços do input sem feedback é frustrante ao usuário e não recomendo. No meu caso real, o campo era um tanto mais complicado e o design já lidava com isso.

Esse exemplo deriva do conceito de `controlled inputs` que está na [documentação oficial do React](https://react.dev/reference/react-dom/components/input#reference). É um exemplo simples do que pode ser feito em React.

Porém, isso entra em conflito com o `react-hook-form`. Tanto eu quanto ele precisamos ter controle das props do componente. Como vamos resolver esse conflito?

A API dele ajuda bastante em alguns casos. Se eu quero adicionar um `onChange` no meu input, eu consigo passar pra ele.

```jsx
<input
  type="text"
  {...form.register('username', {
    onChange: e =>
      console.log(`username agora é ${e.target.value}`)
  })}
/>
```

No caso de controlled component, eu preciso de duas props: value e onChange.

```jsx
<input
  type="text"
  {...form.register('username', {
    value: username,
    onChange:
      e => setUsername(e.target.value.replaceAll(' ', ''))
    validate: validateUsername,
  })}
/>
```

Funcionou! Quer dizer, mais ou menos. Ele não tá validando o form quando atualizo o username. `formState.isValid` sempre está retornando `false`, mesmo quando o username está válido.

## Debugando

Fiquei tentando entender isso durante bastante tempo. Debuguei minha função de validação, fiz várias mudanças nos parâmetros de `useForm` e `register`, refatorei meu input, mas não mudava nada.

Depois de meia hora procurando na documentação, achei um pequeno ponto na API do `register`.

> value: Set up value for the registered input. This prop should be utilised inside useEffect or invoke once, each re-run will update or overwrite the input value which you have supplied.

Então eu tava utilizando `register` de forma errada! Eu não deveria mandar `value` como prop diretamente no componente. Mas então, como faço pra controlar meu componente?

Depois de mais meia hora procurando na documentação, eu achei algo que parecia o que eu queria. E tentei aplicar.

```jsx
function UsernameForm({onSubmit}) {
  const form = useForm()
  const username = form.watch('username')

  useEffect(() => {
    form.register('username', {
      validate: validateUsername,
    })
  }, [form.register])
  
  return (
    <form onSubmit={form.handleSubmit(onSubmit)}>
      <input
        type="text"
        value={username}
        onChange={
          e =>
            form.setValue('username', e.target.value.replaceAll(' ', ''))
        }
      />
      <button type="submit">Salvar</button>
    </form>
  )
}
```

Para ver esse código no doc, precisa acessar [esse link](https://react-hook-form.com/advanced-usage#ControlledmixedwithUncontrolledComponents) e clicar em _Custom register_.


Achei isso incrivelmente frustrante. Na documentação, esse código está num canto escondido da parte avançada. O código também cria vários átomos de código que estão separados, mas que são bem entrelaçadas. E ainda por cima, ele adiciona um `useEffect`, um hook que idealmente seria utilizado pra sincronizar com sistemas *externos*, embora o form seja bem autossuficiente.

A API inicialmente fez algo muito bom, colocar `onChange` no `register` é uma solução simples e completa. Só que criar um `value` que não age como o esperado é uma decisão tão burra que me falta palavras pra descrever. Eu tenho certeza que essa decisão trouxe bugs em aplicações e frustrações para devs.

Ao mesmo tempo, a solução oficial de utilizar um conjunto de `watch`, um `useEffect` com `register`, e um `setValue` parece uma solução tão mal pensada que estou incrédulo até agora. Como `react-hook-form` é algo tão popular se não lida com um caso de uso simples?

## Conclusão

Vou usar Zod puro agora