Menu

useDeferredValue en React: deja que lo lento se quede atrás

useDeferredValue te da una copia de un valor que puede quedarse atrás durante los renderizados pesados, para que un input mantenga una buena respuesta mientras una lista lenta se pone al día. Aprende cómo funciona, el valor inicial de React 19 y cómo se compara con useTransition y el debounce.

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

useDeferredValue te da una copia de un valor que puede quedarse atrás. Cuando el valor cambia, React vuelve a dibujar primero la pantalla con la copia vieja, lo que mantiene la escritura instantánea, y luego renderiza la parte lenta con el valor nuevo en segundo plano.

Escribe una palabra rápido. El input va al día y la lista se atenúa mientras está atrasada. La consola registra una línea SlowList rendering por letra, porque SlowList registra en cuanto React empieza a renderizarlo, pero las listas de los valores intermedios nunca llegan a la pantalla: cada letra nueva interrumpió el renderizado en segundo plano, y React volvió a empezar con el texto más reciente. Cambia query={deferredQuery} por query={query} y vuelve a escribir: ahora cada pulsación espera un cuarto de segundo por la lista.

La sintaxis

const deferredValue = useDeferredValue(value);
const deferredValue = useDeferredValue(value, initialValue); // React 19
  • value es cualquier cosa que renderices: una cadena de un input, un número, un objeto. Los primitivos funcionan mejor, porque un objeto nuevo en cada renderizado siempre cuenta como un cambio.
  • El valor devuelto es igual a value la mayor parte del tiempo. Justo después de que value cambia, todavía contiene el valor anterior durante un renderizado, y se pone al día en un renderizado en segundo plano.

Cómo funciona

Cada cambio produce dos renderizados:

  1. Un renderizado urgente con el value nuevo y el deferredValue viejo. El input muestra el texto nuevo. Los componentes que reciben solo deferredValue reciben las mismas props que antes.
  2. Un renderizado en segundo plano con el deferredValue nuevo. Este es el lento. React lo renderiza en trozos y lo abandona si value vuelve a cambiar antes de que termine, y luego vuelve a empezar con el valor más reciente.

Puedes verlo ocurrir registrando ambos valores:

Escribe una sola letra, por ejemplo a, y la consola imprime dos líneas más: primero text = "a" con deferred = "", y luego ambos "a". Ese primer renderizado es lo que mantiene rápida la escritura, pero solo si la parte lenta puede saltárselo. Por eso SlowList en el primer ejemplo está envuelto en memo: durante el renderizado urgente su prop query no cambió, así que React se lo salta. Sin memo, el renderizado urgente igual ejecutaría toda la lista lenta, solo que con el texto viejo, y no ganarías nada.

Mostrar contenido desactualizado

Mientras el renderizado en segundo plano está en curso, value y el valor diferido son distintos. Compáralos para indicarle al usuario que el contenido está desactualizado, sin ocultarlo:

const isStale = query !== deferredQuery;

<div style={{ opacity: isStale ? 0.5 : 1, transition: 'opacity 0.2s' }}>
    <SlowList query={deferredQuery} />
</div>

Esto suele ser más agradable que un spinner: los resultados viejos siguen legibles y cambian en su lugar cuando los nuevos están listos. La misma idea funciona con Suspense: si el valor diferido hace que un componente se suspenda mientras carga datos, React sigue mostrando el contenido viejo en lugar del fallback hasta que el contenido nuevo está listo (consulta Suspense y lazy).

El valor inicial (React 19)

Por defecto el primer renderizado no tiene nada respecto a lo que quedarse atrás, así que el valor diferido es el valor real desde el principio. Desde React 19 puedes pasar un segundo argumento. En el primer renderizado el hook devuelve initialValue, y luego React vuelve a renderizar en segundo plano con el valor real. Esto te permite mostrar algo barato al instante y completar la parte costosa justo después.

La consola muestra dos renderizados: primero con "", luego con el texto completo. Quita el segundo argumento y solo se registra un renderizado, directamente con el texto completo.

useDeferredValue vs useTransition

Ambos marcan trabajo como no urgente y usan la misma maquinaria de renderizado. La diferencia es dónde los pones:

  • useTransition envuelve la actualización. Llamas a startTransition(() => setQuery(text)), así que necesitas el setter. Recibes isPending sin esfuerzo.
  • useDeferredValue envuelve el valor. Lo llamas donde se usa el valor, así que funciona cuando el valor llega como prop o viene de un hook que no controlas. Calculas tú mismo el "pendiente" con value !== deferredValue.
// You own the state: either works, a transition is direct
startTransition(() => setQuery(nextQuery));

// You only receive the value
function SearchResults({ query }) {
    const deferredQuery = useDeferredValue(query);
    return <SlowList query={deferredQuery} />;
}

useDeferredValue vs debounce y throttle

El debounce espera un tiempo fijo después del último cambio; el throttle actualiza como mucho una vez por intervalo. Ambos eligen un retraso de antemano, así que en una laptop rápida hacen la página más lenta de lo necesario, y en un teléfono lento el retraso puede seguir siendo demasiado corto.

useDeferredValue no tiene retraso. El renderizado en segundo plano empieza de inmediato, así que en un dispositivo rápido la lista se actualiza casi al instante, y en uno lento React mantiene el input con buena respuesta y renderiza la lista tan a menudo como puede. El trabajo en segundo plano además se puede interrumpir, algo que un renderizado con debounce no permite: una vez que un renderizado con debounce empieza, bloquea hasta que termina.

Lo que no hace es reducir las peticiones de red. Si cada valor nuevo inicia un fetch, el valor diferido sigue cambiando con cada renderizado que se completa, así que aplica un debounce a la propia petición cuando importa el volumen de peticiones.

Errores comunes

  • Olvidar memo. Como se mostró arriba, el componente lento debe poder saltarse el renderizado urgente. Si no está envuelto en memo, o una de sus otras props es un objeto nuevo en cada renderizado, renderiza dos veces por pulsación en lugar de una.
  • Pasar un objeto nuevo. useDeferredValue({ query }) crea un objeto nuevo en cada renderizado, así que el valor diferido siempre parece cambiado y React inicia cada vez un renderizado en segundo plano extra. Difiere el primitivo (useDeferredValue(query)) y construye los objetos a partir de él.
  • Esperar menos peticiones. Un componente que obtiene datos según el valor diferido sigue obteniéndolos para cada valor que termina de renderizarse.

Cuándo usarlo

Usa useDeferredValue cuando un input controla una parte de la página que tarda en renderizarse, la parte lenta se puede memoizar, y te parece bien mostrar por un momento resultados algo viejos: resultados de búsqueda, un gráfico que se redibuja a partir de un filtro, una vista previa de texto con formato. Si la parte lenta es lenta por una razón que puedes corregir, como renderizar 5000 filas cuando se ven 50, corrige eso primero. Para la versión de este patrón en la que eres dueño del setter, consulta useTransition.

Preguntas frecuentes

¿Qué hace useDeferredValue?

Devuelve una versión de un valor que puede quedarse atrás respecto al real. Cuando el valor cambia, React renderiza primero con el valor diferido viejo y luego vuelve a renderizar en segundo plano con el nuevo, y ese renderizado en segundo plano se puede interrumpir.

¿Por qué useDeferredValue no hace más rápida mi lista?

El componente lento debe estar envuelto en memo y recibir el valor diferido. Si no, el renderizado urgente igual renderiza el componente lento, solo que con el valor viejo, y escribir sigue siendo lento.

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

useTransition envuelve la actualización de estado, así que necesitas acceso al setter. useDeferredValue envuelve un valor, así que funciona cuando solo recibes el valor, por ejemplo como prop o desde un hook que no controlas.

¿useDeferredValue es un debounce?

No. No hay un retraso fijo. React empieza el renderizado diferido justo después del urgente, y en un dispositivo rápido el desfase casi no se ve. Tampoco reduce por sí solo las peticiones de red.

¿Cuál es el segundo argumento de useDeferredValue?

Desde React 19 puedes pasar un initialValue. En el primer renderizado el hook lo devuelve, y luego React vuelve a renderizar en segundo plano con el valor real.

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

Aprende a programar con Coddy

COMENZAR