DEV Community

Silviu Technology
Silviu Technology

Posted on

React: estados claros para un inbox temporal

Diseñar un inbox temporal en React no consiste solo en mostrar mensajes: consiste en explicar cada estado, respetar el foco y mantener la interfaz rápida.

El problema: esperar no es un estado

En una prueba de registro, una persona puede estar esperando un mensaje de verificación en un inbox temporal. La pantalla suele empezar con un spinner, luego cambia a una lista vacía, después muestra un error o finalmente enseña el correo recibido. Si esos cambios solo se ven como variaciones visuales, el flujo se vuelve confuso para quien usa un lector de pantalla, navega con teclado o tiene una conexión lenta.

El error de diseño mas común es tratar loading, empty y error como detalles del componente. En realidad son información de producto. Cada uno debe responder tres preguntas: qué está pasando, qué puede hacer la persona y cuándo volverá a cambiar la pantalla.

Este enfoque es especialmente útil cuando probamos una burner email address: la bandeja tiene una vida corta, el mensaje puede tardar y repetir la acción sin control puede crear carreras entre solicitudes.

Un pequeño modelo de estados

Antes de escribir JSX, dibujo una máquina sencilla. No hace falta una librería para esto; basta con que el componente tenga una fuente de verdad clara.

type InboxState =
  | { kind: "loading" }
  | { kind: "ready"; messages: Message[] }
  | { kind: "empty"; checkedAt: number }
  | { kind: "error"; message: string };
Enter fullscreen mode Exit fullscreen mode

La ventaja es que el diseño no depende de una combinación imposible de booleanos como isLoading, hasError y hasMessages. Un estado excluye a los otros. Para una bandeja de correo de prueba, también añadiría un identificador de ejecución y una regla de caducidad: así una respuesta vieja no pisa el resultado de la sesión actual.

En el estado empty, conviene decir que la consulta terminó y no encontró mensajes todavía. “Sin mensajes” es distinto de “no hemos podido consultar”. Ese matiz ahorra varios reintentos innecesarios y hace el flujo mas honesto.

Feedback accesible sin mover el foco

La llegada de un mensaje no debería enviar el foco al principio de la página. Si alguien está revisando el formulario o usando el botón de actualizar con teclado, ese salto rompe su contexto. Mantengo el foco donde estaba y anuncio solo el cambio importante mediante una región viva.

function InboxStatus({ state }: { state: InboxState }) {
  const text = {
    loading: "Buscando mensajes…",
    ready: `${state.messages.length} mensaje(s) disponible(s)`,
    empty: "No hay mensajes nuevos todavía",
    error: `No se pudo actualizar: ${state.message}`,
  }[state.kind];

  return (
    <p role="status" aria-live="polite" aria-atomic="true">
      {text}
    </p>
  );
}
Enter fullscreen mode Exit fullscreen mode

role="status" permite comunicar una actualización sin interrumpir de forma agresiva. Los controles visibles siguen siendo necesarios: una región viva no reemplaza un botón “Actualizar” con nombre accesible, un estado de foco perceptible y mensajes de error asociados al control que puede resolverlos.

Para una interfaz mas completa, el botón puede quedar deshabilitado mientras se consulta, pero no debería desaparecer. También muestro una hora de última comprobación en texto normal. Es un detalle pequeño, aunque ayuda a distinguir un inbox vacío de una pantalla que dejó de responder.

Si el usuario pulsa actualizar, cancelo la solicitud anterior o ignoro su resultado con un requestId. Esta protección complementa una validación de email sin respuestas viejas: el principio es el mismo, cada resultado debe pertenecer a la acción que sigue vigente.

Rendimiento: actualizar lo necesario

Un inbox temporal no necesita volver a renderizar toda la aplicación cada pocos segundos. Mantengo la consulta y el estado cerca de la pantalla que los usa, memoizo filas realmente costosas y evito crear estructuras nuevas en cada render si no aportan valor.

También separo la frecuencia de consulta de la animación. Un indicador CSS sutil consume menos atención que un componente que cambia continuamente el árbol de accesibilidad. Cuando llega un mensaje, resalto la fila una sola vez y dejo que la persona decida si quiere abrirla.

El layout debe reservar espacio para el estado de carga y para los mensajes de error. Este patrón reduce saltos visuales y conecta bien con estas notas sobre errores de email sin CLS. No hace falta una transición espectacular; una interfaz estable se siente mas rápida porque conserva las referencias visuales.

Si el producto usa tempmailso como fuente de una dirección temporal para pruebas, lo trato como una dependencia reemplazable. El componente recibe una función getMessages y no conoce los detalles de red. Así los tests pueden devolver empty, error o ready de forma determinista, sin depender de que llegue un correo real.

Checklist para revisar el componente

Antes de darlo por terminado, reviso estos puntos:

  • ¿Cada solicitud tiene un estado visible y un texto comprensible?
  • ¿La llegada de un mensaje se anuncia sin robar el foco?
  • ¿Los botones tienen nombre, foco visible y un tamaño cómodo?
  • ¿El estado vacío explica qué hacer a continuación?
  • ¿Una respuesta vieja puede sobrescribir la sesión actual?
  • ¿El layout mantiene su espacio cuando aparece un error?
  • ¿Puedo probar loading, empty, error y ready sin red?

El texto “dummy e mail” puede aparecer en un caso de prueba o en una nota de QA, pero no debería convertirse en el nombre de un control ni en el mensaje principal de la interfaz. La terminología visible debe ayudar a entender el producto, no repetir una búsqueda poco clara.

La idea final es sencilla: un inbox temporal es un pequeño sistema de estados con una interfaz humana encima. Cuando React expresa esos estados con claridad, Accessibility deja de ser una capa añadida al final. Se convierte en parte del diseño, y el rendimiento pasa a medirse por algo muy concreto: cuánto contexto conserva la persona mientras espera.

Top comments (0)