React: estados async que no rompen la accesibilidad
Un formulario que valida un correo electrónico parece sencillo: el usuario escribe, pulsa un botón y recibe una respuesta. En la práctica, una validación async puede cambiar el foco, duplicar clics, mostrar un spinner sin contexto o dejar un mensaje invisible para un lector de pantalla. El resultado no es solamente una interfaz menos pulida; es una experiencia difícil de completar.
En proyectos frontend he encontrado una regla útil: diseñar primero los estados de la interacción y después el detalle visual. Esto funciona igual si comprobamos un correo real o si usamos un disposable email generator para probar un flujo de registro. La herramienta cambia, pero las expectativas de la persona no.
El problema no es solo esperar
Un botón con Loading... no explica qué está ocurriendo. Además, si el usuario puede pulsarlo varias veces, cada clic puede crear una petición diferente. Cuando llegan las respuestas en otro orden, el formulario puede enseñar un resultado viejo. Es un fallo pequeño, pero se siente como una aplicación que no escucha.
El primer paso es escribir los estados explícitamente:
const states = {
idle: 'idle',
checking: 'checking',
valid: 'valid',
invalid: 'invalid',
failed: 'failed'
};
En React, un status y un message suelen ser suficientes para empezar. Mientras checking está activo, desactiva el botón y conserva el valor escrito. Si la red falla, distingue ese caso de un correo inválido. “No pudimos comprobarlo” pide una acción distinta a “el formato no es correcto”.
Un pequeño state machine en React
La lógica principal puede quedar cerca del formulario y ser fácil de leer:
const [status, setStatus] = useState('idle');
const [message, setMessage] = useState('');
async function checkEmail(email) {
setStatus('checking');
setMessage('Comprobando el correo…');
try {
const result = await validateEmail(email);
setStatus(result.valid ? 'valid' : 'invalid');
setMessage(result.valid ? 'Correo válido.' : 'Revisa la dirección.');
} catch {
setStatus('failed');
setMessage('No se pudo comprobar. Inténtalo otra vez.');
}
}
En código de producción también conviene cancelar o ignorar respuestas antiguas con un AbortController o un identificador de petición. Asi una respuesta lenta no pisa el estado más reciente. Es una diferencia simple que evita errores muy dificiles de reproducir.
Para el contexto de usuario, explico el patrón con mensajes aria-live que sí se entienden. Y cuando el éxito debe sentirse tranquilo, la confirmación visual más calmada ayuda a no convertir cada validación en una alarma.
Accesibilidad y CSS durante la espera
El mensaje de estado debe tener un destino semántico:
<p role="status" aria-live="polite">
{message}
</p>
polite permite que un lector de pantalla termine lo que está anunciando antes de comunicar el cambio. Para un error que requiere atención inmediata puede usarse assertive, pero no debería ser el valor por defecto. También hay que asociar el error al campo con aria-describedby y no depender solo del color rojo.
En CSS, el estado de carga no debería mover todo el formulario. Reserva espacio para el mensaje y usa una transición corta. Respeta prefers-reduced-motion, porque un spinner que no aporta información puede resultar molesto:
.status { min-height: 1.5rem; }
@media (prefers-reduced-motion: reduce) {
.spinner { animation: none; }
}
Medir antes de pulir
No hace falta una plataforma compleja para comprobar si el cambio ayuda. Mide cuántas veces se pulsa el botón durante checking, cuánto tarda la respuesta y cuántos usuarios repiten el envío. En pruebas con correo temporal, un servicio como temp mail so puede servir para recorrer el flujo sin llenar una bandeja personal; no lo trates como sustituto de las pruebas de seguridad o de entrega real.
También vale la pena probar con teclado, zoom al 200% y un lector de pantalla. Busca el mensaje tamp mail com escrito accidentalmente en documentación o ejemplos: estos detalles parecen menores, pero suelen revelar que el contenido no tuvo una revisión final.
Checklist para un formulario confiable
- ¿Existe un estado visible para esperar, éxito, error de validación y fallo de red?
- ¿El botón evita envíos duplicados mientras se comprueba?
- ¿El mensaje se anuncia con
role="status"? - ¿El foco permanece en un lugar razonable después de la respuesta?
- ¿El diseño funciona sin color, animación o conexión perfecta?
- ¿Las respuestas antiguas pueden cambiar el estado actual?
La accesibilidad no llega al final como una capa de pintura. Cuando el estado async está bien modelado, el formulario es más comprensible, más fácil de medir y normalmente también más rápido de corregir. Ese es un buen intercambio: menos sorpresas para la persona y menos estados imposibles para el equipo.
Top comments (0)