Hook useDeferredValue
Diferir la actualización de una parte de la interfaz para mantener el resto responsivo durante renders costosos.
Receta
Tarjeta de referencia rápida — lista para copiar y pegar.
const deferredQuery = useDeferredValue(query);
// El componente que usa deferredQuery se re-renderiza con menor prioridad
<SearchResults query={deferredQuery} />
// Detectar contenido obsoleto
const isStale = deferredQuery !== query;Cuándo usarlo: Tienes un value que cambia rápidamente (como entrada de búsqueda) impulsando un render hijo costoso, y quieres que la entrada permanezca receptiva mientras el hijo se pone al día.
Ejemplo en Funcionamiento
"use client";
import { memo, useDeferredValue, useState } from "react";
const HeavyList = memo(function HeavyList({ query }: { query: string }) {
const items = Array.from({ length: 5000 }, (_, i) => `Result ${i}: ${query}`);
return (
<ul className="max-h-64 overflow-y-auto text-sm">
{items
.filter((item) => item.toLowerCase().includes(query.toLowerCase()))
.slice(0, 100)
.map((item, i) => (
<li key={i} className="py-0.5">{item}</li>
))}
</ul>
);
});
export function DeferredSearch() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
const isStale = deferredQuery !== query;
return (
<div className="space-y-3">
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Escribe para buscar..."
className="border rounded px-3 py-2 w-full"
/>
<div className={isStale ? "opacity-50 transition-opacity" : "transition-opacity"}>
<HeavyList query={deferredQuery} />
</div>
</div>
);
}Lo que esto demuestra:
queryse actualiza instantáneamente en cada pulsación de tecla, manteniendo la entrada receptivadeferredQueryse retrasa, por lo queHeavyListse re-renderiza con menor prioridadReact.memoenHeavyListes esencial — sin él, el componente se re-renderiza conqueryde todas formas- Retroalimentación de obsolescencia visual mediante opacidad cuando
deferredQuery !== query
Profundización
Cómo Funciona
useDeferredValueacepta un value y devuelve una copia diferida del mismo- En el renderizado inicial, el value diferido es igual al value original
- Cuando el value original cambia, React primero re-renderiza con el value diferido antiguo, luego programa un re-renderizado de fondo con el nuevo value
- El re-renderizado de fondo es interrumpible — si el value original cambia nuevamente, React abandona el renderizado en curso y comienza uno nuevo
- Esto funciona con Suspense: si el value diferido hace que un componente se suspenda, el usuario ve el contenido antiguo mientras se carga el contenido nuevo
Parámetros y Valores de Retorno
| Parámetro | Tipo | Descripción |
|---|---|---|
value | T | El value que quieres diferir |
initialValue | T (React 19) | Value inicial opcional para el primer renderizado |
| Retorno | Tipo | Descripción |
|---|---|---|
deferredValue | T | La versión diferida del value |
Variaciones
Diferir contenido de Suspense:
function App() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
return (
<>
<SearchInput value={query} onChange={setQuery} />
<Suspense fallback={<Skeleton />}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}React 19 initialValue (evitar mostrar contenido obsoleto al montar):
// Mostrar resultados vacíos inicialmente, luego llenar con baja prioridad
const deferredItems = useDeferredValue(items, []);Combinando con transition para estado de carga:
const deferredQuery = useDeferredValue(query);
const isStale = deferredQuery !== query;
return (
<div>
{isStale && <p className="text-sm text-gray-400">Cargando...</p>}
<Results query={deferredQuery} />
</div>
);Notas de TypeScript
// El tipo se infiere del value de entrada
const deferredQuery = useDeferredValue(query); // string
// React 19 con initialValue
const deferredItems = useDeferredValue<Item[]>(items, []);Gotchas
-
Olvidar React.memo —
useDeferredValuesolo ayuda si el componente hijo está memoizado. De lo contrario, el hijo se re-renderiza con el value actual en el primer paso de todas formas. Solución: Envuelve el hijo costoso enReact.memo. -
No apto para limitar llamadas API —
useDeferredValuediferir el renderizado, no el cambio del value en sí. No reduce el número de llamadas fetch. Solución: Usadebouncepara llamadas API; usauseDeferredValuepara rendimiento de renderizado. -
Flash de contenido obsoleto — El value diferido se retrasa detrás del value real, así que los usuarios ven contenido obsoleto brevemente. Solución: Muestra un indicador visual (opacidad, spinner) cuando
deferredValue !== value. -
Solo valores primitivos — Pasar un nuevo objeto o array en cada renderizado anula el propósito, porque la comparación
Object.isencuentra un nuevo value cada vez. Solución: Memoiza objetos o arrays conuseMemoantes de pasarlos auseDeferredValue.
Alternativas
| Alternativa | Úsalo Cuando | No lo Uses Cuando |
|---|---|---|
useTransition | Controlas cuándo sucede la actualización de estado y quieres envolverla explícitamente | Recibes el value como prop y no puedes controlar cuándo cambia |
| Debouncing | Quieres reducir el número de actualizaciones de estado o llamadas API | Quieres retroalimentación instantánea con renderizado diferido |
| Virtualización | El cuello de botella es renderizar demasiados nodos DOM | El cuello de botella es computación, no recuento DOM |
| Web Worker | El cálculo pesado debe moverse completamente del hilo principal | El trabajo es renderizado de React |
useDeferredValue vs. useTransition: useTransition envuelve la actualización de estado; useDeferredValue envuelve el consumo del value. Usa useDeferredValue cuando no controlas la actualización de estado (por ejemplo, viene de una prop o padre).
Preguntas Frecuentes
¿Por qué debe el componente hijo estar envuelto en React.memo para que useDeferredValue ayude?
- Sin
React.memo, el hijo se re-renderiza en cada renderizado padre con el value actual, no el diferido. React.memosalta el re-renderizado cuando las props no han cambiado, permitiendo que el value diferido se retrasar.- Sin memoización,
useDeferredValueno proporciona ningún beneficio de renderizado.
¿Cómo detecto que el value diferido es obsoleto y muestro un indicador de carga?
const deferredQuery = useDeferredValue(query);
const isStale = deferredQuery !== query;
return (
<div className={isStale ? "opacity-50" : ""}>
<Results query={deferredQuery} />
</div>
);- Compara el value diferido con el value actual para determinar la obsolescencia.
¿Cuál es la diferencia entre useDeferredValue y debouncing?
- Debouncing retrasa la actualización de estado en sí, reduciendo el número de actualizaciones.
useDeferredValuepermite que la actualización de estado suceda inmediatamente pero diferir cuándo el hijo se re-renderiza.- Usa debouncing para llamadas API; usa
useDeferredValuepara rendimiento de renderizado.
Gotcha: ¿Por qué pasar un nuevo objeto o array a useDeferredValue anula su propósito?
useDeferredValuecompara values conObject.is, que verifica igualdad de referencia.- Un nuevo objeto
{}o array[]creado durante el renderizado siempre tiene una nueva referencia. - Memoiza objetos con
useMemoantes de pasarlos auseDeferredValue.
¿Reduce useDeferredValue el número de llamadas API?
- No. Diferir el renderizado, no el cambio de value o ejecución de fetch.
- Si necesitas menos llamadas API, usa
debounceothrottleen el manejador de entrada. useDeferredValuesolo ayuda con renderizado React costoso.
¿Cómo funciona useDeferredValue con Suspense?
- Si el value diferido hace que un hijo se suspenda, React muestra el contenido antiguo en lugar de un fallback.
- El render de fondo es interrumpible -- si el value cambia nuevamente, React abandona el renderizado en progreso.
- Esto proporciona una transición suave entre contenido antiguo y nuevo.
¿Cuál es el parámetro initialValue de React 19?
// El primer renderizado usa [] en lugar del array de items completo
const deferredItems = useDeferredValue(items, []);- El segundo argumento establece el value diferido para el renderizado inicial.
- En los renderizados posteriores, el value diferido rastrea el value real con baja prioridad.
Gotcha: ¿Puedo usar useDeferredValue sin Suspense?
- Sí.
useDeferredValuefunciona con cualquier componente hijo costoso, no solo los habilitados para Suspense. - Sin Suspense, simplemente diferir el re-renderizado del hijo que recibe el value diferido.
- Suspense agrega el beneficio de mostrar contenido antiguo mientras se carga el contenido nuevo.
¿Cómo escribo useDeferredValue en TypeScript?
// El tipo se infiere del value de entrada
const deferredQuery = useDeferredValue(query); // string
// React 19 con initialValue
const deferredItems = useDeferredValue<Item[]>(items, []);¿Cuándo debo elegir useDeferredValue sobre useTransition?
- Usa
useDeferredValuecuando recibes un value que cambia rápidamente como prop y no controlas la actualización de estado. - Usa
useTransitioncuando controlas el setter de estado y quieres envolverla actualización explícitamente. - Ambas logran el mismo objetivo -- mantener la interfaz receptiva durante renders costosos.
Relacionado
- useTransition — envuelve actualizaciones de estado en lugar de diferir values
- useMemo — memoiza computaciones costosas
- useCallback — estabilizar referencias para hijos memoizados
- useState — la fuente de estado impulsando values diferidos