Um rant sobre API mal feita

andrewmat

André Matulionis

Posted on March 9, 2024

Um rant sobre API mal feita

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.

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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. É 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.

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

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

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

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.

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

Para ver esse código no doc, precisa acessar esse link 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

💖 💪 🙅 🚩
andrewmat
André Matulionis

Posted on March 9, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related