Ir al contenido
angel.library

Suspense, lazy y use

  • <Suspense> muestra un fallback cuando un descendiente se suspende durante el render.
  • lazy() difiere la descarga del módulo de un componente hasta que se intenta renderizar.
  • use(Promise) lee un recurso compatible y se integra con Suspense.
  • Un fetch iniciado dentro de useEffect no activa Suspense automáticamente.
  • El tamaño y la posición del límite son decisiones de experiencia de usuario.
import { Suspense, lazy, useState } from "react"
const MarkdownEditor = lazy(() => import("./MarkdownEditor"))
export function ArticleTools() {
const [editing, setEditing] = useState(false)
return (
<section>
<button onClick={() => setEditing(true)}>Editar</button>
{editing && (
<Suspense fallback={<p>Cargando editor…</p>}>
<MarkdownEditor />
</Suspense>
)}
</section>
)
}

Declara lazy en el nivel del módulo. Si lo declaras dentro del componente, cada render crea otro tipo y puede reiniciar su estado. La Promise debe resolver un módulo cuyo default sea un componente.

Cuando un descendiente todavía no puede renderizar, React busca el <Suspense> más cercano y muestra fallback. Al estar listo, intenta de nuevo el contenido.

Page
├── Header visible
├── Suspense "lista"
│ └── Results puede esperar
└── Footer visible

Un único límite alrededor de toda la página puede ocultar contenido útil por una dependencia pequeña. Demasiados límites generan cambios visuales fragmentados. Agrupa piezas que deban aparecer juntas.

import { Suspense, use } from "react"
type Comment = { id: string; body: string }
function Comments({
commentsPromise
}: {
commentsPromise: Promise<Comment[]>
}) {
const comments = use(commentsPromise)
return (
<ul>
{comments.map((comment) => (
<li key={comment.id}>{comment.body}</li>
))}
</ul>
)
}
export function CommentSection({
commentsPromise
}: {
commentsPromise: Promise<Comment[]>
}) {
return (
<Suspense fallback={<p>Cargando comentarios…</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
)
}

La creación y caché de la Promise deben pertenecer a una capa compatible, normalmente un framework. Crear una Promise nueva en cada render puede reiniciar el trabajo o producir advertencias.

use también puede leer contexto y, a diferencia de los Hooks tradicionales, puede aparecer dentro de una condición. No puede envolverse en try/catch; un error debe llegar a un Error Boundary.

Esto no activa el fallback:

useEffect(() => {
fetch("/api/items").then(/* ... */)
}, [])

El efecto ocurre después del commit. Debes modelar su loading manualmente o utilizar una capa de datos compatible con Suspense. No lances Promises manualmente desde componentes de aplicación sin un contrato de caché y framework.

Una actualización urgente que hace suspender contenido existente puede reemplazarlo por el fallback. startTransition o useDeferredValue permiten tratar la actualización como no urgente y conservar la interfaz anterior mientras llega la siguiente.

const deferredQuery = useDeferredValue(query);
const stale = deferredQuery !== query;
return (
<div aria-busy={stale} style={{ opacity: stale ? 0.6 : 1 }}>
<Suspense fallback={<ResultsSkeleton />}>
<Results query={deferredQuery} />
</Suspense>
</div>
);

El valor diferido no agrega un retraso fijo ni evita requests; cambia la prioridad del render.

Suspense maneja espera; un Error Boundary maneja fallos de render. Si el import de lazy o el recurso rechaza, el error debe llegar a un límite de error del framework o a una clase Error Boundary.

ErrorBoundary
└── Suspense
└── contenido asíncrono

El fallback debe describir el contenido esperado y reservar espacio cuando sea posible. Un spinner genérico para una página completa suele comunicar menos que un skeleton de la región exacta.

  • editor pesado que solo se abre bajo demanda;
  • panel secundario detrás de una pestaña;
  • ruta transmitida por un framework con Server Components;
  • vista costosa que conserva resultados anteriores durante una búsqueda;
  • recurso compartido que un framework entrega como Promise estable.
  • Declarar lazy() dentro del componente.
  • Esperar que Suspense detecte un fetch de useEffect.
  • Crear una Promise en cada render.
  • envolver toda la aplicación en un único fallback.
  • ocultar contenido ya útil durante cada actualización.
  • confundir espera con error y no añadir una estrategia de recuperación.