useDeferredValue dá a você uma cópia de um valor que pode ficar para trás. Quando o valor muda, o React primeiro redesenha a tela com a cópia antiga, o que mantém a digitação instantânea, e depois renderiza a parte lenta com o novo valor em segundo plano.
Digite uma palavra rápido. O input acompanha e a lista fica esmaecida enquanto está atrasada. O Console registra uma linha SlowList rendering por letra, porque SlowList registra assim que o React começa a renderizá-lo, mas as listas dos valores intermediários nunca chegam à tela: cada letra nova interrompeu a renderização em segundo plano, e o React recomeçou com o texto mais recente. Troque query={deferredQuery} por query={query} e digite de novo: cada tecla agora espera um quarto de segundo pela lista.
A sintaxe
const deferredValue = useDeferredValue(value);
const deferredValue = useDeferredValue(value, initialValue); // React 19
valueé qualquer coisa que você renderiza: uma string de um input, um número, um objeto. Valores primitivos funcionam melhor, porque um objeto novo a cada renderização sempre conta como mudança.- O valor retornado é igual a
valuena maior parte do tempo. Logo depois quevaluemuda, ele ainda guarda o valor anterior por uma renderização e alcança o novo em uma renderização em segundo plano.
Como funciona
Cada mudança produz duas renderizações:
- Uma renderização urgente com o novo
valuee odeferredValueantigo. O input mostra o texto novo. Componentes que recebem só odeferredValuerecebem as mesmas props de antes. - Uma renderização em segundo plano com o novo
deferredValue. Essa é a lenta. O React a renderiza em fatias e a abandona sevaluemudar de novo antes de ela terminar, recomeçando com o valor mais recente.
Dá para ver isso acontecendo registrando os dois valores:
Digite uma única letra, por exemplo a, e o Console imprime mais duas linhas: primeiro text = "a" com deferred = "", depois as duas com "a". Essa primeira renderização é o que mantém a digitação rápida, mas só se a parte lenta puder pulá-la. É por isso que SlowList no primeiro exemplo está envolvido em memo: durante a renderização urgente a prop query dele não mudou, então o React o pula. Sem o memo, a renderização urgente ainda executaria a lista lenta inteira, só que com o texto antigo, e você não ganharia nada.
Mostrando conteúdo desatualizado
Enquanto a renderização em segundo plano está em andamento, value e o valor adiado são diferentes. Compare-os para avisar o usuário de que o conteúdo está desatualizado, sem escondê-lo:
const isStale = query !== deferredQuery;
<div style={{ opacity: isStale ? 0.5 : 1, transition: 'opacity 0.2s' }}>
<SlowList query={deferredQuery} />
</div>
Isso costuma ser mais agradável que um spinner: os resultados antigos continuam legíveis e mudam no lugar quando os novos ficam prontos. A mesma ideia funciona com Suspense: se o valor adiado faz um componente suspender enquanto carrega dados, o React continua mostrando o conteúdo antigo em vez do fallback até o novo conteúdo ficar pronto (veja Suspense e lazy).
O valor inicial (React 19)
Por padrão, a primeira renderização não tem nada atrás do que ficar, então o valor adiado é o valor real desde o início. Desde o React 19 você pode passar um segundo argumento. Na primeira renderização o hook retorna initialValue, e o React então renderiza de novo em segundo plano com o valor real. Isso permite mostrar algo barato na hora e preencher a parte cara logo em seguida.
O Console mostra duas renderizações: primeiro com "", depois com o texto completo. Remova o segundo argumento e só uma renderização é registrada, com o texto completo direto.
useDeferredValue vs useTransition
Os dois marcam trabalho como não urgente e usam o mesmo mecanismo de renderização. A diferença é onde você os coloca:
- useTransition envolve a atualização. Você chama
startTransition(() => setQuery(text)), então precisa do setter. Você ganha oisPendingde graça. - useDeferredValue envolve o valor. Você o chama onde o valor é usado, então funciona quando o valor chega como prop ou vem de um hook que você não controla. Você mesmo calcula o "pendente" com
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 e throttle
O debounce espera um tempo fixo depois da última mudança; o throttle atualiza no máximo uma vez por intervalo. Os dois escolhem um atraso de antemão, então em um notebook rápido deixam a página mais lenta do que precisa, e em um celular lento o atraso ainda pode ser curto demais.
O useDeferredValue não tem atraso. A renderização em segundo plano começa na hora, então em um aparelho rápido a lista atualiza quase imediatamente, e em um aparelho lento o React mantém o input responsivo e renderiza a lista com a frequência que conseguir. O trabalho em segundo plano também pode ser interrompido, o que uma renderização com debounce não pode: depois que uma renderização com debounce começa, ela bloqueia até terminar.
O que ele não faz é cortar requisições de rede. Se cada valor novo inicia um fetch, o valor adiado continua mudando a cada renderização que termina, então faça o debounce da própria requisição quando o volume de requisições importar.
Erros comuns
- Esquecer o
memo. Como mostrado acima, o componente lento precisa conseguir pular a renderização urgente. Se ele não está envolvido emmemo, ou se uma das outras props dele é um objeto novo a cada renderização, ele renderiza duas vezes por tecla em vez de uma. - Passar um objeto novo.
useDeferredValue({ query })cria um objeto novo a cada renderização, então o valor adiado sempre parece ter mudado e o React inicia uma renderização extra em segundo plano toda vez. Adie o valor primitivo (useDeferredValue(query)) e monte objetos a partir dele. - Esperar menos requisições. Um componente que busca dados com base no valor adiado continua buscando para cada valor que termina de renderizar.
Quando usar
Use o useDeferredValue quando um input controla uma parte da página que é lenta de renderizar, a parte lenta pode ser memoizada e você não se importa em mostrar resultados um pouco antigos por um instante: resultados de busca, um gráfico que se redesenha a partir de um filtro, a prévia de um texto formatado. Se a parte lenta é lenta por um motivo que você pode corrigir, como renderizar 5.000 linhas quando 50 estão visíveis, corrija isso primeiro. Para a versão deste padrão em que você é dono do setter, veja o useTransition.
Perguntas frequentes
O que o useDeferredValue faz?
Ele retorna uma versão de um valor que pode ficar para trás em relação ao real. Quando o valor muda, o React primeiro renderiza com o valor adiado antigo e depois renderiza de novo em segundo plano com o novo, e essa renderização em segundo plano pode ser interrompida.
Por que o useDeferredValue não deixa minha lista mais rápida?
O componente lento precisa estar envolvido em memo e receber o valor adiado. Caso contrário, a renderização urgente ainda renderiza o componente lento, só que com o valor antigo, e a digitação continua lenta.
Qual é a diferença entre useDeferredValue e useTransition?
O useTransition envolve a atualização de estado, então você precisa ter acesso ao setter. O useDeferredValue envolve um valor, então funciona quando você só recebe o valor, por exemplo como prop ou de um hook que você não controla.
O useDeferredValue é um debounce?
Não. Não há atraso fixo. O React inicia a renderização adiada logo depois da urgente, e em um aparelho rápido o atraso mal aparece. Ele também não reduz requisições de rede por conta própria.
Qual é o segundo argumento do useDeferredValue?
Desde o React 19 você pode passar um initialValue. Na primeira renderização o hook o retorna, e depois o React renderiza de novo em segundo plano com o valor real.