Um rant sobre API mal feita
André Matulionis
Posted on March 9, 2024
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>
)
}
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}`)
})}
/>
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,
})}
/>
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>
)
}
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
Posted on March 9, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.