Hook useTransition
Marca actualizaciones de estado como no urgentes para que no bloqueen la entrada del usuario.
Busca en todas las páginas de la documentación
Marca actualizaciones de estado como no urgentes para que no bloqueen la entrada del usuario.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
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.
"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:
setQuery está fuera de startTransitionisPending muestra un indicador de carga mientras la transición está en progresostartTransition le dice a React que la actualización de estado dentro es no urgenteisPending es true, permitiéndote mostrar un indicador de cargastartTransition también puede manejar funciones asincronadas, haciendo que sea útil para acciones del servidor| 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 |
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
});
}// isPending es siempre boolean, startTransition acepta () => void
const [isPending, startTransition] = useTransition();
// React 19: startTransition también acepta funciones asincronadas
startTransition(async () => {
await serverAction();
});Envolver actualizaciones sincronadas baratas - Usar startTransition para un simple setCount(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 startTransition retrasa la entrada también. Solución: Mantén actualizaciones urgentes (valor de entrada) fuera de startTransition.
isPending se queda true demasiado tiempo - Si la transición provoca que un límite de Suspense se suspenda, isPending permanece 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 setState sincronamente, no dentro de un setTimeout o después de un await. 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 de startTransition no tiene efecto. Solución: Asegúrate de que la actualización de estado es un setter de estado de React.
| 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.
useTransition envuelve la actualización de estado misma, dándote control explícito sobre qué actualizaciones son no urgentes.useDeferredValue envuelve el consumo del valor, difiriendo cuándo un hijo se re-renderiza con el nuevo valor.useTransition cuando controlas la actualización de estado; usa useDeferredValue cuando recibes el valor como prop.startTransition retrasa que la entrada refleje lo que escribe el usuario.isPending se establece automáticamente en true cuando la transición comienza y en false cuando se completa.setLoading(true) / setLoading(false).isPending permanece true mientras se carga el contenido suspendido.useState, setters de useReducer).startTransition no tiene efecto.startTransition(async () => { await serverAction(); setState(result); }) es soportado.setState sincronamente -- sin await dentro.isPending permanece true hasta que el contenido suspendido se resuelva.setCount(count + 1), startTransition añade sobrecarga sin beneficio.const [isPending, startTransition] = useTransition();
// isPending: boolean
// startTransition: (callback: () => void) => void
// React 19 también acepta asincronado:
// startTransition: (callback: () => void | Promise<void>) => voidstartTransition en el mismo manejador se agrupan en una sola transición.startTransition como baja prioridad juntas.Revisado por Chris St. John·Última actualización: 19 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥