Optimiza tus formularios en React: más allá de useState y los básicos de react-hook-form

En los últimos años react-hook-form se ha convertido en la opción preferida para gestionar formularios en React. Si todavía usas useState para cada campo, perderás tiempo y rendimiento. En este artículo vamos a profundizar en técnicas intermedias que suelen pasar desapercibidas: validaciones asíncronas, integración con librerías UI y la creación de hooks reutilizables.
¿Por qué dejar useState?
Controlar cada input con useState funciona, pero cada render implica volver a crear los handlers y a actualizar el árbol de componentes. react-hook-form registra los ref de los elementos y mantiene el estado fuera del árbol, lo que reduce renders innecesarios y mejora la experiencia del usuario en formularios extensos.
Primeros pasos con react-hook-form
Instala la librería y crea un formulario simple. Observa cómo el registro de campos es mucho más conciso que con useState:
import React from 'react';
import { useForm } from 'react-hook-form';
export default function SimpleForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email', { required: 'El email es obligatorio' })} />
{errors.email && <span>{errors.email.message}</span>}
<button type="submit">Enviar</button>
</form>
);
}
Con solo una línea ({...register('email')}) ya tienes el binding, la gestión de errores y la revalidación automática.
Validaciones sincrónicas y asíncronas
Para la mayoría de los campos basta con reglas simples, pero a veces necesitamos comprobar datos en el servidor (por ejemplo, si un nombre de usuario está disponible). react-hook-form permite usar validadores asíncronos mediante la opción validate:
const checkUsername = async username => {
const res = await fetch(/api/users/check?username=${username});
const { available } = await res.json();
return available || 'Ese nombre ya está en uso';
};
// Dentro del registro del input
{...register('username', { required: true, validate: checkUsername })}
El formulario mostrará el mensaje de error tan pronto como la promesa se resuelva, sin bloquear el resto de la UI.
Integrando UI de terceros
Componentes como Material‑UI o Ant Design usan sus propios onChange y a veces no exponen directamente la referencia del input. En esos casos podemos usar el método Controller que provee react-hook-form para conectar cualquier componente controlado.
import { Controller, useForm } from 'react-hook-form';
import { TextField } from '@mui/material';
export default function MuiForm() {
const { control, handleSubmit } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="firstName"
control={control}
defaultValue=""
render={({ field }) => (
<TextField {...field} label="Nombre" variant="outlined" />
)}
/>
<button type="submit">Enviar</button>
</form>
);
}
El Controller se encarga de pasar value y onChange al componente externo, manteniendo la lógica de validación intacta.
Creando un hook reutilizable
Si trabajas con varios formularios que comparten lógica (por ejemplo, cargar opciones desde una API o resetear campos después de enviar), puedes encapsular esa lógica en un hook propio:
import { useForm } from 'react-hook-form';
import { useEffect } from 'react';
export function useUserForm(initialValues = {}) {
const { register, handleSubmit, reset, setError, formState } = useForm({ defaultValues: initialValues });
// Cargar datos de usuario al montar
useEffect(() => {
async function fetchUser() {
const res = await fetch('/api/user/me');
const data = await res.json();
reset(data);
}
fetchUser();
}, [reset]);
const onSubmit = async data => {
try {
const res = await fetch('/api/user', { method: 'PUT', body: JSON.stringify(data) });
if (!res.ok) throw new Error('Error al actualizar');
} catch (e) {
setError('api', { type: 'manual', message: e.message });
}
};
return { register, handleSubmit: handleSubmit(onSubmit), formState, errors: formState.errors };
}
Con este hook, cualquier componente que necesite el formulario de usuario solo tiene que llamarlo y renderizar los campos habituales.
Conclusión
Pasar de useState a react-hook-form es sólo el primer paso. Aprovechar las validaciones asíncronas, conectar UI de terceros con Controller y crear hooks reutilizables te permitirá escalar tus formularios sin perder rendimiento ni claridad. La próxima vez que tengas que crear un formulario complejo, prueba estas técnicas y verás cómo tu código se vuelve más limpio y mantenible.