Menu

Hook useTransition en React: mantén la UI fluida

useTransition marca una actualización de estado como no urgente, para que React mantenga la página con buena respuesta mientras renderiza la parte lenta en segundo plano. Aprende isPending, startTransition, las transiciones async de React 19 y por qué no es un debounce.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

useTransition te permite marcar una actualización de estado como no urgente. React renderiza esa actualización en segundo plano y la descarta o la reinicia si ocurre algo más urgente, como un clic o una pulsación de tecla, así que la página mantiene una buena respuesta mientras una parte lenta de la pantalla se pone al día.

Haz clic en posts y luego, antes de que aparezca la lista, haz clic en contact. Contact aparece al instante: React descartó el renderizado a medio terminar de los posts. Ahora cambia startTransition(() => { setTab(next); }) por un simple setTab(next); y prueba lo mismo: la página se congela unos 300 ms después de hacer clic en posts, y tu clic en contact espera hasta que terminan los posts.

La sintaxis

const [isPending, startTransition] = useTransition();

startTransition(() => {
    setTab(next);
});
  • startTransition recibe una función y la ejecuta enseguida. Todas las actualizaciones de estado que contiene se marcan como transiciones.
  • isPending es true desde el momento en que inicias la transición hasta que React terminó de renderizarla y confirmó el resultado.

useTransition no recibe argumentos. La función que pasas se ejecuta de forma síncrona; la transición trata de cómo renderiza React la actualización resultante, no de cuándo se ejecuta tu función.

Qué significa "no urgente"

React divide las actualizaciones en dos tipos. Las actualizaciones urgentes, como escribir, hacer clic y pulsar, tienen que reflejarse de inmediato o la página parece rota. Las actualizaciones de transición, como cambiar a una vista nueva o mostrar resultados filtrados, pueden tardar un momento.

React renderiza una transición en pequeños trozos y comprueba entre componentes si llegó algo urgente. Si llegó, React pausa o abandona la transición, atiende la actualización urgente y luego vuelve a empezar la transición con el estado más reciente. Mientras se renderiza una transición, la pantalla anterior se queda; React no muestra un resultado a medio construir. Por eso el ejemplo sigue mostrando el texto de about, con la pista Loading junto a los botones, hasta que los posts están listos.

La interrupción ocurre entre componentes. Un solo componente que bloquea durante 300 ms no se puede cortar a la mitad, y por eso el trabajo lento de arriba está repartido en 300 pequeños componentes SlowPost.

Mostrar una pista de pendiente

isPending es el lugar para un indicador de carga que no reemplaza el contenido actual: un spinner junto al botón, texto atenuado, una etiqueta "Saving...". En el primer ejemplo muestra Loading... junto a las pestañas. También puedes usarlo para dar estilo al contenido:

<div style={{ opacity: isPending ? 0.6 : 1 }}>{content}</div>

Filtrar una lista grande mientras se escribe

No puedes poner en una transición el propio valor de un input. Un <input> controlado debe recibir su nuevo valor en la misma actualización urgente, o se pierden caracteres. En su lugar, guarda dos piezas de estado: el texto del input (urgente) y la búsqueda que usa la lista (una transición).

Escribe 123 rápido. El input nunca se retrasa. La consola igual registra una línea rendering results por pulsación, porque Results registra en cuanto React empieza a renderizarlo, pero las listas de 1 y 12 nunca llegan a la pantalla: cada pulsación más nueva interrumpió el renderizado en curso, y React volvió a empezar con la búsqueda más reciente. Results está envuelto en memo a propósito: sin él, el renderizado urgente de cada pulsación también renderizaría la lista lenta con la búsqueda vieja, y perderías la ventaja.

startTransition sin el hook

react también exporta startTransition como una función normal. Marca las actualizaciones como transiciones igual que el hook, pero no recibes isPending. Úsalo donde no puedes llamar a un hook, por ejemplo en una librería de datos o en un router fuera de cualquier componente.

import { startTransition } from 'react';

function navigate(url) {
    startTransition(() => {
        setCurrentUrl(url);
    });
}

Transiciones async en React 19

Desde React 19 la función que pasas puede ser async. isPending se queda entonces en true todo el tiempo que se ejecuta la función, incluida la espera, lo que lo convierte en una forma simple de mostrar un estado pendiente durante una petición. El saveName falso de abajo sustituye a una petición real.

Fíjate en el segundo startTransition alrededor de setSaved. React pierde el rastro de la transición a través de un await, así que una actualización de estado después de él tiene que volver a envolverse para contar como parte de la transición. React llama acción a una función pasada de esta forma; useActionState y las acciones de formulario se construyen sobre el mismo mecanismo.

Una transición no es un debounce

Un debounce espera un tiempo fijo después de la última pulsación antes de hacer algo. Una transición no espera. Empieza a renderizar de inmediato, y si llega una actualización más nueva descarta el trabajo viejo. En un dispositivo rápido el resultado aparece sin ningún retraso; en uno lento, React hace todo lo que puede entre pulsaciones. Lo que una transición no puede hacer es reducir el número de peticiones de red: si cada pulsación inicia un fetch, aplica un debounce al fetch.

Transiciones y Suspense

Las transiciones también cambian lo que hace un límite Suspense. Normalmente, cuando un componente dentro de un límite se suspende (un componente lazy que carga, o datos leídos con use), React reemplaza el contenido por el fallback. Si la actualización que causó la suspensión era una transición, React conserva en su lugar el contenido que ya está en pantalla, e isPending se queda en true hasta que el contenido nuevo está listo. Así evitan los routers mostrar un spinner del tamaño de la página en cada navegación: envuelven la navegación en una transición, y la página vieja sigue visible hasta que la siguiente puede renderizarse. El contenido que nunca se mostró sigue usando el fallback, así que un límite que se revela por primera vez se comporta como siempre.

Cosas que no funcionan

  • Actualizaciones dentro de un timeout. startTransition(() => setTimeout(() => setTab(next), 100)) no marca la actualización, porque se ejecuta cuando startTransition ya terminó. Pon startTransition dentro del timeout.
  • Un solo componente lento. React solo puede interrumpir entre componentes, así que un componente que bloquea durante mucho tiempo sigue bloqueando.
  • Controlar un input. Como se dijo arriba, el propio estado del input se queda urgente.

Para el caso en que no eres dueño del setter de estado y solo recibes un valor, como una prop, useDeferredValue te da el mismo comportamiento.

Preguntas frecuentes

¿Qué hace useTransition?

Te da startTransition, que marca como no urgentes las actualizaciones de estado que contiene, e isPending, que es true mientras esa actualización todavía se está renderizando. React puede interrumpir un renderizado no urgente para atender primero los clics y lo que se escribe.

¿Cuál es la diferencia entre useTransition y startTransition?

Ambos marcan actualizaciones como transiciones. El hook useTransition además te da isPending para una pista de carga. El startTransition independiente que se importa de react funciona fuera de los componentes pero no tiene indicador de pendiente.

¿Puedo usar useTransition para un input de texto?

No para el propio valor del input. Un input controlado debe actualizarse de forma síncrona o la escritura se rompe. Mantén urgente el estado del input y pon en una transición la actualización costosa que depende de él, o usa useDeferredValue.

¿useTransition es lo mismo que un debounce?

No. Un debounce espera un tiempo fijo antes de hacer algo. Una transición empieza a renderizar de inmediato y se interrumpe si llega una actualización más nueva, así que en un dispositivo rápido el resultado aparece sin ningún retraso.

¿startTransition puede recibir una función async?

Sí, desde React 19. isPending se queda en true hasta que termina la función async. Las actualizaciones de estado después de un await deben envolverse en otra llamada a startTransition para marcarse como transición.

Ilustración de los lenguajes de programación de Coddy

Aprende a programar con Coddy

COMENZAR