useDeferredValue

useDeferredValue é um Hook do React que permite adiar a atualização de uma parte da UI.

const deferredValue = useDeferredValue(value)

Referência

useDeferredValue(value, initialValue?)

Chame useDeferredValue no nível superior do seu componente para obter uma versão adiada desse valor.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}

Veja mais exemplos abaixo.

Parâmetros

  • value: O valor que você deseja adiar. Ele pode ter qualquer tipo.
  • opcional initialValue: Um valor a ser usado durante a renderização inicial de um componente. Se esta opção for omitida, useDeferredValue não adiará durante a renderização inicial, pois não há uma versão anterior de value que ele possa renderizar em vez disso.

Retorna

  • currentValue: Durante a renderização inicial, o valor adiado retornado será o initialValue ou o mesmo que o valor fornecido. Durante as atualizações, o React tentará primeiro uma nova renderização com o valor antigo (então retornará o valor antigo) e, em seguida, tentará outra nova renderização em segundo plano com o novo valor (então retornará o valor atualizado).

Ressalvas

  • Quando uma atualização está dentro de uma Transition, useDeferredValue sempre retorna o novo value e não gera uma renderização adiada, pois a atualização já está adiada.

  • Os valores que você passa para useDeferredValue devem ser valores primitivos (como strings e números) ou objetos criados fora da renderização. Se você criar um novo objeto durante a renderização e passá-lo imediatamente para useDeferredValue, ele será diferente em cada renderização, causando re-renderizações desnecessárias em segundo plano.

  • Quando useDeferredValue recebe um valor diferente (comparado com Object.is), além da renderização atual (quando ainda usa o valor anterior), ele agenda uma nova renderização em segundo plano com o novo valor. A nova renderização em segundo plano é interrompível: se houver outra atualização para o value, o React reiniciará a nova renderização em segundo plano do zero. Por exemplo, se o usuário estiver digitando em uma entrada mais rápido do que um gráfico que recebe seu valor adiado pode renderizar novamente, o gráfico só renderizará novamente depois que o usuário parar de digitar.

  • useDeferredValue está integrado com <Suspense>. Se a atualização em segundo plano causada por um novo valor suspender a UI, o usuário não verá o fallback. Eles verão o valor adiado antigo até que os dados sejam carregados.

  • useDeferredValue por si só não impede solicitações de rede extras.

  • Não há atraso fixo causado pelo próprio useDeferredValue. Assim que o React terminar a renderização original, o React começará a trabalhar imediatamente na renderização em segundo plano com o novo valor adiado. Quaisquer atualizações causadas por eventos (como digitação) interromperão a nova renderização em segundo plano e terão prioridade sobre ela.

  • A nova renderização em segundo plano causada por useDeferredValue não dispara Effects até que seja confirmada na tela. Se a nova renderização em segundo plano suspender, seus Effects serão executados após o carregamento dos dados e as atualizações da UI.


Uso

Exibindo conteúdo obsoleto enquanto o conteúdo novo está carregando

Chame useDeferredValue no nível superior do seu componente para adiar a atualização de parte da sua UI.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}

Durante a renderização inicial, o valor adiado será o mesmo que o valor que você forneceu.

Durante as atualizações, o valor adiado “ficará para trás” do valor mais recente. Em particular, o React primeiro renderizará novamente sem atualizar o valor adiado e, em seguida, tentará renderizar novamente em segundo plano com o valor recém-recebido.

Vamos analisar um exemplo para ver quando isso é útil.

Note

Este exemplo supõe que você use uma fonte de dados compatível com Suspense:

  • Obtenção de dados com estruturas compatíveis com Suspense como Relay e Next.js
  • Carregamento lento do código do componente com lazy
  • Leitura do valor de uma Promise com use

Saiba mais sobre Suspense e suas limitações.

Neste exemplo, o componente SearchResults suspende enquanto busca os resultados da pesquisa. Tente digitar "a", esperar pelos resultados e, em seguida, editar para "ab". Os resultados de "a" são substituídos pelo fallback de carregamento.

import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Pesquisar álbuns:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Carregando...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );
}

Um padrão de UI alternativo comum é adiar a atualização da lista de resultados e continuar mostrando os resultados anteriores até que os novos resultados estejam prontos. Chame useDeferredValue para passar uma versão adiada da consulta:

export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Pesquisar álbuns:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Carregando...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}

O query atualizará imediatamente, para que a entrada exiba o novo valor. No entanto, o deferredQuery manterá seu valor anterior até que os dados sejam carregados, então SearchResults mostrará os resultados obsoletos por um tempo.

Digite "a" no exemplo abaixo, espere os resultados carregarem e, em seguida, edite a entrada para "ab". Observe como, em vez do fallback Suspense, você agora vê a lista de resultados obsoletos até que os novos resultados sejam carregados:

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  return (
    <>
      <label>
        Pesquisar álbuns:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Carregando...</h2>}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}

Deep Dive

Como adiar um valor funciona por baixo dos panos?

Você pode pensar que isso acontece em duas etapas:

  1. Primeiro, o React renderiza novamente com o novo query ("ab") mas com o deferredQuery antigo (ainda "a") O valor deferredQuery, que você passa para a lista de resultados, é adiado: ele “fica para trás” do valor query.

  2. Em segundo plano, o React tenta renderizar novamente com ambos query e deferredQuery atualizados para "ab" Se esta nova renderização for concluída, o React exibirá na tela. No entanto, se ele suspender (os resultados de "ab" ainda não foram carregados), o React abandonará esta tentativa de renderização e tentará novamente esta nova renderização após o carregamento dos dados. O usuário continuará vendo o valor adiado obsoleto até que os dados estejam prontos.

A renderização “em segundo plano” adiada é interrompível. Por exemplo, se você digitar na entrada novamente, o React a abandonará e reiniciará com o novo valor. O React sempre usará o valor fornecido mais recente.

Observe que ainda há uma solicitação de rede por cada pressionamento de tecla. O que está sendo adiado aqui é a exibição de resultados (até que estejam prontos), não as próprias solicitações de rede. Mesmo que o usuário continue digitando, as respostas de cada pressionamento de tecla são armazenadas em cache, portanto, pressionar Backspace é instantâneo e não busca novamente.


Indicando que o conteúdo está obsoleto

No exemplo acima, não há indicação de que a lista de resultados para a última consulta ainda está carregando. Isso pode ser confuso para o usuário se os novos resultados demorarem um pouco para carregar. Para tornar mais óbvio para o usuário que a lista de resultados não corresponde à consulta mais recente, você pode adicionar uma indicação visual quando a lista de resultados obsoleta for exibida:

<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>

Com essa alteração, assim que você começar a digitar, a lista de resultados obsoleta fica ligeiramente esmaecida até que a nova lista de resultados carregue. Você também pode adicionar uma transição CSS para atrasar o esmaecimento para que pareça gradual, como no exemplo abaixo:

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <>
      <label>
        Pesquisar álbuns:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Carregando...</h2>}>
        <div style={{
          opacity: isStale ? 0.5 : 1,
          transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear'
        }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}


Adiar a nova renderização de uma parte da UI

Você também pode aplicar useDeferredValue como uma otimização de desempenho. É útil quando uma parte da sua UI é lenta para renderizar novamente, não há uma maneira fácil de otimizá-la e você deseja impedir que ela bloqueie o restante da UI.

Imagine que você tem um campo de texto e um componente (como um gráfico ou uma lista longa) que renderiza dinamicamente a cada pressionamento de tecla:

function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={text} />
</>
);
}

Primeiro, otimize SlowList para pular a nova renderização quando suas props forem as mesmas. Para fazer isso, envolva-o em memo:

const SlowList = memo(function SlowList({ text }) {
// ...
});

No entanto, isso só ajuda se os props SlowList forem os mesmos que durante a renderização anterior. O problema que você está enfrentando agora é que é lento quando eles são diferentes e quando você realmente precisa mostrar uma saída visual diferente.

Especificamente, o principal problema de desempenho é que, sempre que você digita na entrada, o SlowList recebe novas props, e a nova renderização de toda a sua árvore torna a digitação desajeitada. Nesse caso, useDeferredValue permite que você priorize a atualização da entrada (que deve ser rápida) em vez de atualizar a lista de resultados (que pode ser mais lenta):

function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}

Isso não torna a nova renderização do SlowList mais rápida. No entanto, ele diz ao React que a nova renderização da lista pode ser despriorizada para que não bloqueie as teclas. A lista “ficará para trás” da entrada e, em seguida, “alcançará”. Como antes, o React tentará atualizar a lista o mais rápido possível, mas não impedirá o usuário de digitar.

A diferença entre useDeferredValue e nova renderização não otimizada

Example 1 of 2:
Nova renderização da lista adiada

Neste exemplo, cada item no componente SlowList é artificialmente desacelerado para que você possa ver como useDeferredValue permite que você mantenha a entrada responsiva. Digite na entrada e observe que a digitação parece ágil enquanto a lista “fica para trás” dela.

import { useState, useDeferredValue } from 'react';
import SlowList from './SlowList.js';

export default function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={deferredText} />
    </>
  );
}

Pitfall

Esta otimização exige que o SlowList seja envolvido em memo. Isso ocorre porque sempre que o text muda, o React precisa ser capaz de renderizar novamente o componente pai rapidamente. Durante essa nova renderização, o deferredText ainda tem seu valor anterior, então SlowList é capaz de pular a nova renderização (seus props não foram alterados). Sem memo, ele teria que renderizar novamente de qualquer maneira, derrotando o objetivo da otimização.

Deep Dive

Como adiar um valor é diferente de debouncing e throttling?

Existem duas técnicas comuns de otimização que você pode ter usado antes neste cenário:

  • Debouncing significa que você esperaria até que o usuário parasse de digitar (por exemplo, por um segundo) antes de atualizar a lista.
  • Throttling significa que você atualizaria a lista de vez em quando (por exemplo, no máximo uma vez por segundo).

Embora essas técnicas sejam úteis em alguns casos, useDeferredValue é mais adequado para otimizar a renderização porque é profundamente integrado ao próprio React e se adapta ao dispositivo do usuário.

Ao contrário de debouncing ou throttling, ele não requer a escolha de nenhum atraso fixo. Se o dispositivo do usuário for rápido (por exemplo, um laptop poderoso), a nova renderização adiada aconteceria quase imediatamente e não seria perceptível. Se o dispositivo do usuário for lento, a lista “ficará para trás” da entrada proporcionalmente a quão lento é o dispositivo.

Além disso, ao contrário de debouncing ou throttling, as novas renderizações adiadas feitas pelo useDeferredValue são interrompíveis por padrão. Isso significa que, se o React estiver no meio de renderizar novamente uma lista grande, mas o usuário fizer outra pressionada de tecla, o React abandonará essa nova renderização, tratará a pressionada de tecla e, em seguida, começará a renderizar novamente em segundo plano. Em contraste, debouncing e throttling ainda produzem uma experiência instável porque são bloqueantes: eles meramente adiam o momento em que a renderização bloqueia a pressionada de tecla.

Se o trabalho que você está otimizando não acontecer durante a renderização, debouncing e throttling ainda são úteis. Por exemplo, eles podem permitir que você dispare menos solicitações de rede. Você também pode usar essas técnicas juntas.