Menu

useDeferredValue no React: adie as partes lentas

useDeferredValue dá a você uma cópia de um valor que pode ficar para trás durante renderizações pesadas, para que um input continue responsivo enquanto uma lista lenta se atualiza. Entenda como funciona, o valor inicial do React 19 e como ele se compara ao useTransition e ao debounce.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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 value na maior parte do tempo. Logo depois que value muda, 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:

  1. Uma renderização urgente com o novo value e o deferredValue antigo. O input mostra o texto novo. Componentes que recebem só o deferredValue recebem as mesmas props de antes.
  2. Uma renderização em segundo plano com o novo deferredValue. Essa é a lenta. O React a renderiza em fatias e a abandona se value mudar 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 o isPending de 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 em memo, 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.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

COMEÇAR