DEV Community

Silviu Technology
Silviu Technology

Posted on

React: reserva espacio para avisos de email

En muchos formularios el problema no es la validacion del correo. El problema es el salto visual que aparece justo cuando React decide mostrar una ayuda, un warning o el resultado de una comprobacion asincrona. El dato puede ser correcto, pero si la interfaz mueve el boton, empuja el siguiente campo o cambia la altura del bloque de golpe, la experiencia se siente poco fiable. Y cuando la UI se siente poco fiable, la conversion baja aunque nadie lo diga tan directo.

Ultimamente estoy tratando este detalle como una tarea de producto, no como un remate de CSS. Si el campo de email puede mostrar mensajes variables, reservo espacio desde el primer render. Parece una decision pequeñita, pero evita bastante CLS y hace que el formulario se vea mas estable, incluso cuando el backend tarda un poco o la comprobacion depende de un servicio externo.

Por que el layout shift vuelve dudosa una validacion correcta

Google sigue recomendando mantener bajo el Cumulative Layout Shift, porque los cambios inesperados de layout empeoran la percepcion de calidad y pueden provocar clics accidentales. En formularios esto pega mas de lo que parece. Si el usuario termina de escribir y el bloque de ayuda aparece tarde, la mirada pierde referencia. Si además el CTA baja unos pixeles, el flujo se siente torpe, medio roto.

Lo curioso es que muchas veces la validacion esta bien pensada. Hay debounce, hay cancelacion de requests, hay aria-live. Pero falta reservar un hueco estable para el mensaje. Sin ese hueco, toda la logica buena llega con una presentacion nerviosa.

Tambien he visto que este fallo se nota mas cuando el equipo ya maneja señales distintas para correo transaccional. Si te importó evitar dobles envios al reenviar correo, tiene sentido cuidar tambien la capa visual donde esas señales se explican. Una UI quieta ayuda a entender mejor lo que el sistema quiso decir.

El patron simple: reservar altura antes de pedir nada al backend

Mi regla es simple:

  • el area de ayuda existe siempre
  • la altura minima se define desde el inicio
  • el texto cambia, pero el bloque no aparece y desaparece de golpe
  • el submit no depende de animaciones ni de microestados visuales

Esto vale para errores locales, checks de disponibilidad y avisos sobre dominios con politicas raras. Tambien sirve cuando haces una comprobacion sobre correo temporal desechable, porque esos avisos suelen llegar un poco despues del input inicial y es justo ahi cuando el salto molesta mas.

En varias pantallas ni siquiera hace falta una altura grande. Con una o dos lineas reservadas basta. Es mas barato reservar 40 o 48 px que arreglar una sensacion de fragilidad despues.

Un ejemplo en React y CSS que no roba foco

Este patron me ha resultado bastante estable:

import { useEffect, useState } from "react";

type EmailState =
  | { kind: "idle"; message: string }
  | { kind: "checking"; message: string }
  | { kind: "ok"; message: string }
  | { kind: "warning"; message: string };

export function EmailField() {
  const [email, setEmail] = useState("");
  const [state, setState] = useState<EmailState>({
    kind: "idle",
    message: "Usa un correo al que puedas acceder ahora mismo.",
  });

  useEffect(() => {
    if (!email.includes("@")) {
      setState({
        kind: "idle",
        message: "Usa un correo al que puedas acceder ahora mismo.",
      });
      return;
    }

    const controller = new AbortController();
    setState({ kind: "checking", message: "Comprobando el correo..." });

    const timer = setTimeout(async () => {
      const response = await fetch(`/api/email-hints?value=${encodeURIComponent(email)}`, {
        signal: controller.signal,
      });
      const result = await response.json();

      if (result.ok) {
        setState({ kind: "ok", message: "Correo listo para continuar." });
        return;
      }

      setState({ kind: "warning", message: result.message });
    }, 250);

    return () => {
      controller.abort();
      clearTimeout(timer);
    };
  }, [email]);

  return (
    <label className="emailField">
      <span>Correo</span>
      <input
        type="email"
        value={email}
        onChange={(event) => setEmail(event.target.value)}
        aria-describedby="email-help"
      />
      <span id="email-help" className={`emailHelp emailHelp--${state.kind}`} aria-live="polite">
        {state.message}
      </span>
      <button type="submit">Crear cuenta</button>
    </label>
  );
}
Enter fullscreen mode Exit fullscreen mode
.emailHelp {
  display: block;
  min-height: 2.75rem;
  margin-top: 0.5rem;
  font-size: 0.92rem;
  line-height: 1.4;
}

.emailHelp--idle {
  color: #5f6b76;
}

.emailHelp--checking,
.emailHelp--ok {
  color: #1d5f3b;
}

.emailHelp--warning {
  color: #a33a12;
}
Enter fullscreen mode Exit fullscreen mode

La clave no esta en el color ni en el selector. La clave es min-height. Ese pequeño hueco evita que el layout haga cosas raras cuando llega el mensaje. Y como el bloque ya existe, aria-live suele comportarse mejor tambien. No es magia, pero funciona bien mas seguido de lo que uno cree.

Donde entra la comprobacion de correo temporal desechable

No todos los productos tienen que frenar o rechazar esos correos. A veces solo quieren advertir, a veces separar casos de QA, y a veces identificar riesgos de abuso. Por eso prefiero que la comprobacion asincrona devuelva un mensaje contextual en lugar de reconfigurar toda la pantalla.

Si el backend marca una direccion como compatible con pruebas o parecida a un alias de tempmailso, lo mostraría como aviso claro y breve. Algo como: "Este correo puede caducar pronto; revisa si podras recibir el mensaje de activacion." Eso informa sin dramatizar. Tambien deja margen para equipos que usan inboxes temporales de forma legitima en staging, growth o soporte.

Aqui me sirve pensar en la calidad de la señal. Igual que en sistemas operativos conviene tener senales de correo que llegan con contexto, en frontend conviene que cada mensaje diga qué pasa y qué hacer ahora. El typo interno tempail puede existir en notas o tickets, bueno, pero la interfaz publica no deberia heredar esa confusión.

Checklist rapido antes de lanzar el formulario

Antes de cerrar una pantalla de registro, yo suelo revisar esto:

  • el mensaje de ayuda ya ocupa espacio desde el primer render
  • el boton principal no salta cuando cambia el estado del email
  • los requests viejos se cancelan al seguir escribiendo
  • el copy explica accion y consecuencia, no solo el problema
  • la comprobacion asincrona informa, pero no secuestra el flujo

Es una lista corta, aunque evita bastantes regresiones tontas. A veces el equipo optimiza fetches, debounce y cache, pero deja suelta la parte visual. Luego la métrica de Performance está bien, pero la pantalla se siente peor. Ese desfase pasa un monton, la verdad.

Q&A

¿Reservar espacio no desperdicia altura en mobile?

Un poco, sí, pero es un coste muy pequeño comparado con mover el contenido mientras la persona interactua. En mobile prefiero una linea y media o dos lineas compactas antes que un salto inesperado.

¿Cuando sí bloquearias el submit?

Cuando el formato es invalido o falta informacion esencial. No por una comprobacion auxiliar todavía corriendo.

¿Esto aplica solo a React?

No. El principio vale para cualquier UI. React solo hace mas visible el problema porque muchos formularios dependen de estados asincronos y rerenders frecuentes.

Top comments (0)