Hook useTransition
Marca actualizaciones de estado como no urgentes para que no bloqueen la entrada del usuario.
Receta
Tarjeta de referencia rápida — lista para copiar y pegar.
const [isPending, startTransition] = useTransition();
// Envuelve una actualización de estado lenta
startTransition(() => {
setSearchResults(filterLargeList(query));
});
// Muestra el estado pendiente
{isPending && <Spinner />}Cuándo usarlo: Una actualización de estado provoca un re-renderizado costoso (filtrar una lista grande, cambiar de pestañas con contenido pesado) y quieres que la interfaz se mantenga responsiva durante la actualización.
Ejemplo en funcionamiento
"use client";
import { useState, useTransition } from "react";
const ALL_ITEMS = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
category: ["Electronics", "Books", "Clothing", "Food"][i % 4],
}));
export function FilterableList() {
const [query, setQuery] = useState("");
const [filtered, setFiltered] = useState(ALL_ITEMS);
const [isPending, startTransition] = useTransition();
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const value = e.target.value;
setQuery(value); // Urgente: actualiza la entrada inmediatamente
startTransition(() => {
// No urgente: filtra la lista grande
setFiltered(
ALL_ITEMS.filter((item) =>
item.name.toLowerCase().includes(value.toLowerCase())
)
);
});
}
return (
<div className="space-y-3">
<input
value={query}
onChange={handleChange}
placeholder="Busca entre 10,000 elementos..."
className="border rounded px-3 py-2 w-full"
/>
{isPending && <p className="text-sm text-gray-500">Actualizando...</p>}
<ul className="max-h-64 overflow-y-auto text-sm">
{filtered.slice(0, 100).map((item) => (
<li key={item.id} className="py-0.5">
{item.name} — {item.category}
</li>
))}
</ul>
<p className="text-xs text-gray-400">{filtered.length} resultados</p>
</div>
);
}Lo que demuestra esto:
- Escribir en la entrada es instantáneo porque
setQueryestá fuera destartTransition - El filtrado costoso se ejecuta en una transición, para que React pueda interrumpirlo si el usuario vuelve a escribir
isPendingmuestra un indicador de carga mientras la transición está en progreso- La interfaz nunca se congela, ni siquiera con 10,000 elementos
Exploración profunda
Cómo funciona
startTransitionle dice a React que la actualización de estado dentro es no urgente- React comienza a renderizar el nuevo estado pero puede interrumpirlo si llegan actualizaciones de mayor prioridad (como escribir)
- Mientras la transición está pendiente,
isPendingestrue, permitiéndote mostrar un indicador de carga - Las transiciones se integran con Suspense — si un componente dentro de la transición se suspende, React muestra el contenido anterior en lugar de un fallback
- En React 19,
startTransitiontambién puede manejar funciones asincronadas, haciendo que sea útil para acciones del servidor
Parámetros y valores de retorno
| Parámetro | Tipo | Descripción |
|---|---|---|
| (ninguno) | — | useTransition no toma parámetros |
| Retorno | Tipo | Descripción |
|---|---|---|
isPending | boolean | true mientras la transición está en progreso |
startTransition | (callback: () => void) => void | Envuelve actualizaciones de estado para marcarlas como no urgentes |
Variaciones
Cambio de pestañas con Suspense:
const [tab, setTab] = useState("home");
const [isPending, startTransition] = useTransition();
function selectTab(nextTab: string) {
startTransition(() => {
setTab(nextTab);
});
}
return (
<div>
<nav className={isPending ? "opacity-50" : ""}>
<button onClick={() => selectTab("home")}>Inicio</button>
<button onClick={() => selectTab("posts")}>Publicaciones</button>
</nav>
<Suspense fallback={<Spinner />}>
{tab === "home" ? <Home /> : <Posts />}
</Suspense>
</div>
);Transiciones asincronadas de React 19 con acciones del servidor:
const [isPending, startTransition] = useTransition();
function handleSubmit() {
startTransition(async () => {
const result = await saveToServer(formData);
setData(result); // La interfaz se actualiza después de que el servidor responde
});
}Notas de TypeScript
// isPending es siempre boolean, startTransition acepta () => void
const [isPending, startTransition] = useTransition();
// React 19: startTransition también acepta funciones asincronadas
startTransition(async () => {
await serverAction();
});Escollos
-
Envolver actualizaciones sincronadas baratas — Usar
startTransitionpara un simplesetCount(count + 1)añade sobrecarga sin beneficio. Solución: Solo usa transiciones para actualizaciones que provoquen re-renderizados costosos. -
No separar actualizaciones urgentes de no urgentes — Envolver tanto el estado de la entrada como el estado del filtro en
startTransitionretrasa la entrada también. Solución: Mantén actualizaciones urgentes (valor de entrada) fuera destartTransition. -
isPending se queda true demasiado tiempo — Si la transición provoca que un límite de Suspense se suspenda,
isPendingpermanece true hasta que el contenido suspendido se resuelva. Solución: Este es el comportamiento esperado; diseña tus estados de carga en consecuencia. -
startTransition debe ser sincronizado (React 18) — En React 18, el callback debe llamar a
setStatesincronamente, no dentro de unsetTimeouto después de unawait. Solución: En React 18, dispara la actualización de estado sincronamente. En React 19, los callbacks asincronos son soportados. -
No se puede envolver estado no-React — Las transiciones solo funcionan con actualizaciones de estado de React (
useState,useReducer). Actualizar una ref o un store externo dentro destartTransitionno tiene efecto. Solución: Asegúrate de que la actualización de estado es un setter de estado de React.
Alternativas
| Alternativa | Úsalo cuando | No lo uses cuando |
|---|---|---|
useDeferredValue | Quieres diferir un valor específico sin controlar cuándo se dispara la actualización | Necesitas control explícito sobre qué actualizaciones son no urgentes |
| Debouncing | Reducir la frecuencia de operaciones costosas (p. ej., llamadas a API) | Quieres que React se mantenga responsivo durante el render mismo |
| Web Worker | El cálculo consume mucha CPU y no debería bloquear el hilo principal en absoluto | El trabajo es renderizar componentes React |
Virtualización (react-window) | Renderizar miles de elementos DOM | El cuello de botella es el cálculo, no los nodos DOM |
Transiciones vs. debouncing: Debouncing retrasa la actualización completamente. Las transiciones dejan que React comience a renderizar inmediatamente pero interrumpen si algo más urgente llega. Las transiciones mantienen la interfaz anterior visible mientras se renderiza la nueva.
Preguntas frecuentes
¿Cuál es la diferencia entre useTransition y useDeferredValue?
useTransitionenvuelve la actualización de estado misma, dándote control explícito sobre qué actualizaciones son no urgentes.useDeferredValueenvuelve el consumo del valor, difiriendo cuándo un hijo se re-renderiza con el nuevo valor.- Usa
useTransitioncuando controlas la actualización de estado; usauseDeferredValuecuando recibes el valor como prop.
¿Por qué debería mantener la actualización del estado de entrada fuera de startTransition?
- Envolver la actualización del valor de entrada en
startTransitionretrasa que la entrada refleje lo que escribe el usuario. - Solo la actualización costosa descendente (filtrado, renderizado de contenido pesado) debería estar en la transición.
- Separar urgente (entrada) de no urgente (resultados) mantiene la interfaz responsiva.
¿Cómo difiere isPending de un estado de carga manejado manualmente?
isPendingse establece automáticamente entruecuando la transición comienza y enfalsecuando se completa.- No necesitas boilerplate
setLoading(true)/setLoading(false). - También se integra con límites de Suspense --
isPendingpermanece true mientras se carga el contenido suspendido.
Escollo: ¿Puedo usar startTransition con estado no-React (refs, stores externos)?
- No. Las transiciones solo funcionan con actualizaciones de estado de React (
useState, setters deuseReducer). - Actualizar una ref o un store externo dentro de
startTransitionno tiene efecto. - La actualización de estado debe llamar a un setter de estado de React para ser reconocida como una transición.
¿Puede startTransition aceptar una función asincronada?
- En React 19, sí.
startTransition(async () => { await serverAction(); setState(result); })es soportado. - En React 18, el callback debe llamar a
setStatesincronamente -- sinawaitdentro. - Las transiciones asincronadas en React 19 hacen que las acciones del servidor sean transparentes.
¿Cómo interactúa useTransition con Suspense?
- Cuando una transición provoca que un componente se suspenda, React muestra el contenido anterior en lugar del fallback de Suspense.
isPendingpermanecetruehasta que el contenido suspendido se resuelva.- Esto evita que la interfaz flashe estados de carga durante cambios de pestaña o cambios de ruta.
Escollo: ¿Envolver una actualización de estado barata en startTransition mejora el rendimiento?
- No. Para actualizaciones simples como
setCount(count + 1),startTransitionañade sobrecarga sin beneficio. - Las transiciones están diseñadas para actualizaciones que provoquen re-renderizados costosos (listas grandes, componentes pesados).
- Solo usa transiciones cuando puedas medir un problema de capacidad de respuesta.
¿Cómo escribo useTransition en TypeScript?
const [isPending, startTransition] = useTransition();
// isPending: boolean
// startTransition: (callback: () => void) => void
// React 19 también acepta asincronado:
// startTransition: (callback: () => void | Promise<void>) => void¿Cómo difiere useTransition de debouncing?
- Debouncing retrasa la actualización completamente -- el usuario espera antes de que suceda algo.
- Las transiciones dejan que React comience a renderizar inmediatamente pero interrumpen si algo más urgente llega.
- Las transiciones mantienen la interfaz anterior visible mientras se renderiza la nueva, proporcionando una experiencia más suave.
¿Puedo anidar múltiples llamadas a startTransition?
- Múltiples llamadas a
startTransitionen el mismo manejador se agrupan en una sola transición. - React trata todas las actualizaciones de estado dentro de
startTransitioncomo baja prioridad juntas. - No hay concepto de niveles de prioridad de transición -- todas las transiciones son igualmente no urgentes.
Relacionado
- useDeferredValue — difiere un valor en lugar de envolver una actualización
- useState — actualizaciones de estado envueltas por transiciones
- useActionState — las acciones de formulario de React 19 usan transiciones internamente
- useOptimistic — muestra estado optimista durante transiciones