DEV Community

Silviu Technology
Silviu Technology

Posted on

React: estados de carga accesibles y veloces

Una interfaz puede completar una petición correctamente y aun así sentirse rota. El problema suele estar en el estado de carga: el botón desaparece, el foco salta al principio de la página o el mensaje de éxito llega sin que un lector de pantalla lo anuncie.

En React, un estado de carga bien diseñado debe hacer tres cosas a la vez: explicar qué está ocurriendo, conservar el contexto de la persona y evitar renders o solicitudes innecesarias. Es un detalle pequeño, pero cambia bastante la percepción de calidad.

El estado de carga también comunica

Un spinner por sí solo dice poco. Puede significar que se está validando un formulario, esperando una respuesta del servidor o actualizando una lista. Para una persona que usa teclado o tecnología de asistencia, la ambigüedad es todavía mas costosa.

Conviene separar los estados que la interfaz necesita comunicar:

  • idle: todavía no se inició ninguna acción.
  • loading: la solicitud está en curso y el control debe evitar envíos duplicados.
  • success: la operación terminó y hay una consecuencia visible.
  • error: la persona puede entender el problema y corregirlo.

En un formulario de verificación, por ejemplo, "Comprobando el código..." es más útil que una animación sin texto. Si el flujo usa un correo temporal desechable como dato de prueba, una dirección desechable puede aislar la recepción mientras la interfaz muestra el mismo feedback claro que mostraría en un escenario real. Un texto como fake e mail com puede aparecer en un fixture de QA, pero no debe convertirse en el mensaje que recibe una persona.

Un patrón pequeño para React

No hace falta una máquina de estados grande para una interacción sencilla. Lo importante es que la transición y el contenido visible estén cerca uno del otro:

function VerifyButton({ onVerify }) {
  const [state, setState] = React.useState("idle");

  async function handleClick() {
    if (state === "loading") return;

    setState("loading");
    try {
      await onVerify();
      setState("success");
    } catch {
      setState("error");
    }
  }

  const label = {
    idle: "Verificar código",
    loading: "Verificando...",
    success: "Código verificado",
    error: "Intentar de nuevo"
  }[state];

  return (
    <button type="button" disabled={state === "loading"} onClick={handleClick}>
      {label}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

El guard clause evita un segundo envío si la persona hace doble clic. disabled comunica que el control está ocupado, aunque no debe ser la unica señal: el resultado también necesita una región de estado. En componentes reales, además conviene cancelar o ignorar una respuesta antigua si la persona cambia de pantalla antes de que termine la solicitud.

Accesibilidad: anunciar sin mover el foco

El foco debería quedarse en el botón o en el campo que inició la acción mientras la petición está en curso. Moverlo automáticamente a un spinner suele ser desconcertante, especialmente cuando el contenido cambia con rapidez.

Un mensaje separado puede usar aria-live:

<p role="status" aria-live="polite">
  {state === "loading" && "Verificando el código..."}
  {state === "success" && "Listo. El código fue verificado."}
  {state === "error" && "No pudimos verificarlo. Revisa el código e inténtalo otra vez."}
</p>
Enter fullscreen mode Exit fullscreen mode

role="status" ya expresa una actualización no urgente. Evita anunciar cada cambio de una barra de progreso si no aporta información. Para errores que bloquean la acción, un resumen visible junto al campo puede ser más útil que un anuncio agresivo.

También hay que conservar el contexto visual. Un botón que cambia de ancho porque el texto "Verificando..." es mas corto puede mover otros controles. Un ancho mínimo o una etiqueta estable reduce ese movimiento sin ocultar el estado.

Este criterio complementa un feedback de verificación sin saltos: la respuesta no solo debe existir, debe aparecer donde la persona espera encontrarla.

Rendimiento: menos trabajo, mejor percepción

La accesibilidad y el rendimiento suelen apoyarse en la misma decisión: renderizar solo lo necesario. Un spinner complejo, una animación permanente y varios efectos que escuchan la misma solicitud pueden hacer que una espera corta se sienta pesada.

Algunas decisiones prácticas:

  1. Mantén el estado de la petición en el componente más cercano al flujo.
  2. Evita guardar en estado valores que se pueden derivar de state.
  3. Respeta prefers-reduced-motion en transiciones y skeletons.
  4. No reemplaces todo el formulario por un loader si basta con desactivar el control y mostrar un mensaje.
  5. Mide los renders en React DevTools antes de añadir memoización.

En pruebas automatizadas, usar un temp mailid como etiqueta accidental puede ocultar un problema de localización. Las pruebas deberían comprobar el texto de estado, el atributo disabled, el foco y la transición final, no solamente que aparezca un spinner.

Cómo probar el flujo completo

Una prueba útil cubre la secuencia, no solo el resultado final:

  • activa la acción con teclado;
  • comprueba que el foco permanece en el control;
  • verifica que un segundo clic no crea otra solicitud;
  • espera el mensaje de éxito o error;
  • repite con una respuesta lenta y con una excepción;
  • revisa el contraste y el movimiento con reducción de animación.

Si el flujo necesita correos de prueba, es razonable aislar correos de prueba por entorno para que los datos de una ejecución no contaminen otra. El componente React debería recibir una respuesta controlada, sin depender de que una bandeja externa esté disponible durante cada test.

Checklist final

  • ¿La persona sabe que la acción empezó?
  • ¿El foco conserva un lugar predecible?
  • ¿El botón evita solicitudes duplicadas?
  • ¿El éxito y el error tienen texto claro?
  • ¿La región aria-live anuncia solo cambios relevantes?
  • ¿La animación respeta las preferencias del sistema?
  • ¿El test cubre red lenta, error y reintento?

Un estado de carga no es decoración. Es parte del contrato de la interfaz: explica el trabajo, protege la interacción y da una señal verificable cuando termina. Con un modelo simple en React, un anuncio moderado y algo de atención al rendimiento, la espera se vuelve más comprensible para todas las personas.

Top comments (0)