Saltar al contenido

Suspense y use(): componentes que esperan

¿Qué significa que un componente «suspenda»?

Al terminar vas a poder explicar

  • Explicar qué pasa físicamente cuando un componente suspende
  • Ubicar un límite de Suspense donde produzca la mejor experiencia
  • Distinguir use() de un hook común y decir por qué puede ir dentro de un if

«Suspense muestra un cargando mientras esperás datos». Eso es lo que hace, no lo que es. Lo que es resulta bastante más interesante:

Suspender es interrumpir el render lanzando una promesa en vez de un valor.

El mismo mecanismo que los Error Boundaries

Cuando un componente suspende, throwea una promesa. React atrapa eso en el bucle de trabajo, sube por la cadena de punteros return buscando el <Suspense> más cercano, descarta el trabajo a medio hacer y renderiza el fallback.

Es exactamente el unwinding de los Error Boundaries. La única diferencia es qué se lanza: un Error deja el fallback puesto para siempre; una promesa hace que React se suscriba a ella y reintente cuando resuelva.

el bucle de trabajo, simplificado
1try {2  beginWork(fiber);3} catch (algo) {4  if (typeof algo.then === "function") {5    // Es una promesa: buscar el <Suspense> más cercano.6    const limite = subirHasta(fiber, esSuspense);7    limite.mostrarFallback = true;8    algo.then(() => reintentar(limite));   // ← la diferencia9  } else {10    // Es un error: buscar el Error Boundary más cercano.11    const limite = subirHasta(fiber, esLimiteDeError);12    limite.error = algo;13  }14}

use(): el hook que no es un hook

React 19 estandarizó esto con use(). Le pasás una promesa y te devuelve su valor, suspendiendo hasta que resuelva.

1function Detalle({ recurso }) {2  const datos = use(recurso);     // suspende hasta que resuelva3  return <p>{datos.nombre}</p>;4}5 6<Suspense fallback={<Esqueleto />}>7  <Detalle recurso={promesa} />8</Suspense>

Y acá aparece algo que rompe una regla que aprendiste: use() se puede llamar dentro de un if.

No es una excepción arbitraria. Volvé a la lección de useContext: la regla existe porque los hooks se identifican por su posición en una lista enlazada. use() no guarda nada en esa lista — igual que useContext. Sin nodo en la cadena, no hay posición que se pueda desalinear.

React real

Suspense leyendo una promesa con use()

El componente lanza la promesa y React muestra el fallback. Cuando resuelve, vuelve a renderizar con el valor.

Código

1const { Suspense, use } = React;2 3let promesa = null;4let disparar = null;5function accion() { if (disparar) disparar(); }6 7function pedirDatos() {8  return new Promise(r => setTimeout(() => r("datos del servidor"), 1200));9}10 11function Detalle({ recurso }) {12  // use() no es un hook común: suspende el componente hasta que resuelva.13  const datos = use(recurso);14  return <p>Llegó: {datos}</p>;15}16 17function App() {18  const [recurso, setRecurso] = useState(() => (promesa ??= pedirDatos()));19 20  useEffect(() => {21    disparar = () => setRecurso(pedirDatos());22  });23 24  return (25    <div>26      <Suspense fallback={<p>Cargando</p>}>27        <Detalle recurso={recurso} />28      </Suspense>29      <button onClick={() => disparar()}>pedir de nuevo</button>30    </div>31  );32}

Corriendo de verdad

bajando React 19.2.0 de esm.sh…

Registro0 commits

Todo lo que imprimas con console.log aparece acá. Los commits los reporta el Profiler de React de verdad.

Apretá «Pedir de nuevo». Vas a ver el fallback, después el valor. Fijate en el registro: hay un commit para el fallback y otro para el contenido. Dos commits, dos pintados — no es una pausa.

Dónde poner el límite

La ubicación del <Suspense> define qué se reemplaza por el fallback. Es una decisión de experiencia, no técnica.

1// ✗ Un dato lento borra la página entera.2<Suspense fallback={<PantallaCompleta />}>3  <Encabezado />4  <Contenido />5  <Comentarios />        {/* el lento */}6</Suspense>7 8// ✓ Sólo la parte lenta muestra su esqueleto.9<Encabezado />10<Contenido />11<Suspense fallback={<EsqueletoComentarios />}>12  <Comentarios />13</Suspense>

La regla práctica: poné el límite lo más abajo posible, envolviendo sólo lo que de verdad puede tardar. Un límite alto es cómodo de escribir y horrible de usar.

Antes de seguir · predecí

Un componente con estado local queda dentro de un límite de Suspense y suspende. ¿Qué pasa con su estado?

Lo que te llevás

Suspender es lanzar una promesa e interrumpir el render. React desenrolla hasta el <Suspense> más cercano, descarta el trabajo y reintenta cuando resuelve. Es el mismo mecanismo que los Error Boundaries con otra carga. use() puede ir dentro de un condicional porque no ocupa lugar en la cadena de hooks.

Antes de marcarla, comprobá

  • ¿Podés explicar explicar qué pasa físicamente cuando un componente suspende?
  • ¿Podés explicar ubicar un límite de Suspense donde produzca la mejor experiencia?
  • ¿Podés explicar distinguir use() de un hook común y decir por qué puede ir dentro de un if?