Una de las cosas mas molestas en formularios modernos no es el error en si. Es el error que llega tarde. La persona escribe su email, corrige una letra, y doscientos milisegundos despues aparece un mensaje sobre la version anterior del campo. El resultado se siente medio roto, aunque el backend este bien.
En equipos frontend lo he visto bastante en checks de disponibilidad, onboarding o validaciones que hablan con un API. El bug no suele ser dramatico, pero si desgasta: mensajes que cambian de golpe, spinners que duran de mas, lectores de pantalla que anuncian estados ya obsoletos. Si alguna vez mezclaste pruebas con una cuenta de QA o con un servicio para generar correo desechable, seguro viste ese ruido amplificado.
Este enfoque me gusta porque ataca UX, accesibilidad y rendimiento al mismo tiempo. Va bastante en la linea de construir feedback de email estable y de evitar errores de email que no cansan, pero aqui el foco esta en respuestas async viejas.
Que problema crean las respuestas viejas
El escenario clasico es este:
- La persona escribe
ana@empresa.co. - El cliente dispara un chequeo remoto.
- La persona corrige a
ana@empresa.com. - La primera respuesta llega ultima y pisa el estado nuevo.
De pronto el formulario marca error cuando el valor visible ya es correcto. No parece gravisimo, pero rompe confianza. Segun NN/g, los mensajes de error deben ser oportunos y precisos. Si el mensaje describe un valor que ya no existe, falla en las dos cosas.
Tambien pega al rendimiento percibido. web.dev insiste bastante en reducir trabajo innecesario durante la interaccion. Si cada tecla abre una carrera nueva, la UI hace de mas y el codigo se vuelve mas fragil, incluso cuando "funciona" en local.
Una separacion simple entre escritura y chequeo remoto
La mejora mas util que he aplicado es separar tres momentos:
- escritura local del campo
- validacion basica de formato
- chequeo remoto solo para el valor vigente
Parece obvio, pero no siempre se modela asi. Muchas implementaciones guardan el valor, validan regex, hacen fetch y actualizan el mensaje en el mismo efecto. Luego llegan los parches: un booleano mas, un timeout mas, un if raro por ahi. Es funcional, pero cuesta leerlo y cuesta mas depurarlo.
Yo prefiero pensar el campo como un mini flujo con fases chicas. No necesitas una gran state machine formal, aunque si ayuda mucho tener estados con nombre. Tambien conviene decidir cuando lanzar el chequeo. En muchos productos, blur o una pequena pausa de escritura es suficiente. Lanzarlo por cada tecla casi nunca paga lo que cuesta, la verdad.
Un patron de React para ignorar resultados obsoletos
Esta version usa una request id simple. No es la unica forma, pero es clara y barata:
type EmailStatus =
| { phase: "idle"; value: string }
| { phase: "format-error"; value: string; message: string }
| { phase: "checking"; value: string }
| { phase: "ok"; value: string }
| { phase: "remote-error"; value: string; message: string };
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function EmailField() {
const [status, setStatus] = useState<EmailStatus>({ phase: "idle", value: "" });
const requestIdRef = useRef(0);
async function checkEmail(value: string) {
const requestId = ++requestIdRef.current;
if (!emailPattern.test(value)) {
setStatus({ phase: "format-error", value, message: "Escribe un correo valido." });
return;
}
setStatus({ phase: "checking", value });
const response = await fetch(`/api/email-check?email=${encodeURIComponent(value)}`);
const data = await response.json();
if (requestId !== requestIdRef.current) return;
setStatus(
data.ok
? { phase: "ok", value }
: { phase: "remote-error", value, message: "No pudimos verificar este correo ahora." }
);
}
}
La linea importante es if (requestId !== requestIdRef.current) return;. Con eso ignoras respuestas que ya nacieron viejas. A veces tambien uso AbortController, sobre todo si el endpoint es pesado, pero incluso sin abortar ya eliminaste el error mas visible para la persona usuaria.
En React moderno, otra mejora fina es envolver la actualizacion visual menos urgente en startTransition si notas que el feedback remoto compite con otras partes del formulario. No hace milagros, pero ayuda a que el input siga sintiendose rapido. En formularios grandes se nota bastante, o almenos yo lo note varias veces.
Detalles de accesibilidad y rendimiento que si importan
Aqui es donde muchos bugs pequenos se vuelven experiencia real:
- Mantener una zona de ayuda fija debajo del input evita saltos de layout.
- Activar
aria-invalidsolo en error real reduce anuncios innecesarios. - Un
aria-live="polite"para estados remotos suele ir mejor que uno agresivo. - Si llega una respuesta vieja, no deberia cambiar ni texto ni icono.
Tambien cuido mucho el lenguaje del mensaje. "Email invalido" no siempre alcanza. Si el problema es formato, dilo. Si el problema es que no se pudo verificar, dilo tambien. Mezclar ambos mensajes hace que soporte, QA y producto lean cosas distintas del mismo fallo, y eso luego se paga.
El keyword raro tempail me aparecio una vez en datos de prueba de un equipo y me sirvio para recordar algo: los formularios tienen que tolerar entradas imperfectas sin volverse teatrales. Corregir no deberia sentirse como pelear contra la interfaz.
Checklist de implementacion
Antes de cerrar una historia de este tipo, reviso esto:
- el estado remoto nunca pisa un valor mas nuevo
- el mensaje de ayuda no mueve el layout
- el input sigue usable con teclado y lector de pantalla
- la validacion local corre antes que el fetch
- las respuestas lentas no dejan un spinner infinito
- las pruebas cubren escritura rapida, blur y correccion inmediata
No es una lista elegante, pero si evita varios regresiones muy tontas.
Q&A
Conviene validar en onChange o en blur?
Depende del coste del chequeo y del riesgo de ruido. Para formato basico, onBlur suele ser suficiente. Para sugerencias locales, onChange esta bien. Para red, yo prefiero una pausa corta o blur, casi siempre.
Hace falta abortar requests?
Si puedes, si. Pero no lo trataria como requisito para arreglar UX. Ignorar respuestas obsoletas ya resuelve gran parte del problema visible. Abortarlas despues ayuda a bajar trabajo y logs innecesarios, que tampoco viene mal.
Top comments (0)