React & Frontend

Guia Completo de Concurrent Mode em React: Suspense, Transitions e useDeferredValue

14 min de leitura

Guia Completo de Concurrent Mode em React: Suspense, Transitions e useDeferredValue

Introdução ao Concurrent Mode O Concurrent Mode é uma das features mais transformadoras introduzidas pelo React nos últimos anos. Diferentemente do modo tradicional, onde o React renderiza componentes de forma síncrona e bloqueia a thread principal, o Concurrent Mode permite que o React interrompa, pause e retome renderizações. Isso significa que operações de longa duração não congelam a interface do usuário, mantendo a responsividade mesmo em aplicações complexas. Para entender a importância disso, imagine um formulário com validação pesada ou uma lista com milhares de itens. No React tradicional, a renderização dessa lista poderia travar a interface por segundos. Com Concurrent Mode, o React pode pausar essa renderização, processar cliques do usuário ou outras prioridades, e depois retomar. Isso não é magia — é uma reimplementação do algoritmo de reconciliação do React, construído sobre um scheduler mais sofisticado. Suspense: Manipulando Carregamento Assincronamente O que é Suspense e como funciona Suspense é um componente que permite que você especifique o que

<h2>Introdução ao Concurrent Mode</h2>

<p>O Concurrent Mode é uma das features mais transformadoras introduzidas pelo React nos últimos anos. Diferentemente do modo tradicional, onde o React renderiza componentes de forma síncrona e bloqueia a thread principal, o Concurrent Mode permite que o React interrompa, pause e retome renderizações. Isso significa que operações de longa duração não congelam a interface do usuário, mantendo a responsividade mesmo em aplicações complexas.</p>

<p>Para entender a importância disso, imagine um formulário com validação pesada ou uma lista com milhares de itens. No React tradicional, a renderização dessa lista poderia travar a interface por segundos. Com Concurrent Mode, o React pode pausar essa renderização, processar cliques do usuário ou outras prioridades, e depois retomar. Isso não é magia — é uma reimplementação do algoritmo de reconciliação do React, construído sobre um scheduler mais sofisticado.</p>

<h2>Suspense: Manipulando Carregamento Assincronamente</h2>

<h3>O que é Suspense e como funciona</h3>

<p>Suspense é um componente que permite que você especifique o que mostrar enquanto os dados ainda estão sendo carregados. Funciona capturando uma Promise lançada durante a renderização e pausando aquele componente até que a Promise seja resolvida. Quando a Promise resolve, o React retoma a renderização com os dados disponíveis.</p>

<p>A ideia central é simples: em vez de usar <code>useEffect</code> com estados <code>loading</code> e <code>error</code>, o componente lança uma Promise durante a renderização. O Suspense acima na árvore captura essa Promise e mostra um fallback até que ela resolva. Isso inverte o fluxo tradicional e torna o código muito mais limpo.</p>

<pre><code class="language-jsx">// Exemplo simples: um componente que lança uma Promise

const fetchUser = (id) =&gt; {

let status = &#039;pending&#039;;

let result;

const promise = fetch(/api/users/${id})

.then(res =&gt; res.json())

.then(data =&gt; {

status = &#039;success&#039;;

result = data;

})

.catch(err =&gt; {

status = &#039;error&#039;;

result = err;

});

return {

read() {

if (status === &#039;pending&#039;) throw promise;

if (status === &#039;error&#039;) throw result;

return result;

}

};

};

const userResource = fetchUser(1);

function UserProfile() {

const user = userResource.read();

return &lt;div&gt;{user.name}&lt;/div&gt;;

}

export default function App() {

return (

&lt;Suspense fallback={&lt;div&gt;Carregando usuário...&lt;/div&gt;}&gt;

&lt;UserProfile /&gt;

&lt;/Suspense&gt;

);

}</code></pre>

<h3>Casos de uso práticos</h3>

<p>Suspense brilha em cenários onde você precisa carregar dados antes de renderizar um componente. Code splitting é o caso mais comum: você quer carregar um componente dinamicamente, e enquanto o bundle não chega, mostra um loading. Suspense também é excelente para orquestrar múltiplas requisições de dados, evitando o &quot;loading em cascata&quot; onde cada componente carrega seus dados independentemente.</p>

<p>Na prática, você raramente criará o padrão de resource como mostrei acima. Bibliotecas como React Query e SWR já integram com Suspense nativamente. Veja como fica com React Query:</p>

<pre><code class="language-jsx">import { useQuery } from &#039;@tanstack/react-query&#039;;

function UserProfile({ userId }) {

const { data: user } = useQuery({

queryKey: [&#039;user&#039;, userId],

queryFn: () =&gt; fetch(/api/users/${userId}).then(r =&gt; r.json()),

suspense: true,

});

return &lt;div&gt;{user.name}&lt;/div&gt;;

}

export default function App() {

return (

&lt;Suspense fallback={&lt;div&gt;Carregando...&lt;/div&gt;}&gt;

&lt;UserProfile userId={1} /&gt;

&lt;/Suspense&gt;

);

}</code></pre>

<h2>Transitions: Mantendo a Interface Responsiva</h2>

<h3>Entendendo transições e prioridades</h3>

<p>Nem todas as atualizações de estado são iguais. Quando o usuário digita em um input, essa atualização é <strong>urgente</strong> — o React deve processar imediatamente para manter a sensação de responsividade. Mas quando essa digitação desencadeia uma busca em uma lista filtrada, essa busca é <strong>menos urgente</strong>. Se o React dedicar todo seu tempo à busca, o input pode ficar travado.</p>

<p>Transitions permitem marcar certos updates como menos urgentes, dando ao React permissão para interrompê-los se houver algo mais importante. A API é simples: use <code>useTransition</code> e envolva o setState com <code>startTransition</code>.</p>

<pre><code class="language-jsx">import { useTransition, useState } from &#039;react&#039;;

function SearchUsers() {

const [query, setQuery] = useState(&#039;&#039;);

const [results, setResults] = useState([]);

const [isPending, startTransition] = useTransition();

const handleChange = (e) =&gt; {

const value = e.target.value;

// Update do input é urgente

setQuery(value);

// Busca é menos urgente

startTransition(() =&gt; {

const filtered = heavyFilter(value);

setResults(filtered);

});

};

return (

&lt;div&gt;

&lt;input

type=&quot;text&quot;

value={query}

onChange={handleChange}

placeholder=&quot;Digite para buscar...&quot;

/&gt;

{isPending &amp;&amp; &lt;span&gt;Buscando...&lt;/span&gt;}

&lt;ul&gt;

{results.map(user =&gt; (

&lt;li key={user.id}&gt;{user.name}&lt;/li&gt;

))}

&lt;/ul&gt;

&lt;/div&gt;

);

}

function heavyFilter(query) {

// Simula uma operação pesada

const mockUsers = Array.from({ length: 10000 }, (_, i) =&gt; ({

id: i,

name: User ${i}

}));

return mockUsers.filter(u =&gt;

u.name.toLowerCase().includes(query.toLowerCase())

);

}

export default SearchUsers;</code></pre>

<h3>Navegação com transições</h3>

<p>Um caso de uso extremamente comum é a navegação. Quando o usuário clica em um link, você quer mudar a rota imediatamente (para feedback visual), mas carregar o novo conteúdo pode ser pesado. Transições lidam com isso perfeitamente:</p>

<pre><code class="language-jsx">import { useTransition } from &#039;react&#039;;

import { useNavigate } from &#039;react-router-dom&#039;;

function Navigation() {

const navigate = useNavigate();

const [isPending, startTransition] = useTransition();

const handleNavigation = (path) =&gt; {

startTransition(() =&gt; {

navigate(path);

});

};

return (

&lt;nav&gt;

&lt;button

onClick={() =&gt; handleNavigation(&#039;/home&#039;)}

disabled={isPending}

&gt;

Home {isPending &amp;&amp; &#039;(carregando...)&#039;}

&lt;/button&gt;

&lt;button

onClick={() =&gt; handleNavigation(&#039;/about&#039;)}

disabled={isPending}

&gt;

Sobre {isPending &amp;&amp; &#039;(carregando...)&#039;}

&lt;/button&gt;

&lt;/nav&gt;

);

}

export default Navigation;</code></pre>

<p>O <code>isPending</code> retornado por <code>useTransition</code> indica se há uma transição em andamento. Use isso para desabilitar botões, mostrar spinners ou manter o conteúdo anterior na tela enquanto o novo está sendo preparado.</p>

<h2>useDeferredValue: Adiando Computações Custosas</h2>

<h3>Quando adiar valores é mais simples que transições</h3>

<p>Enquanto <code>useTransition</code> marca uma ação como menos urgente, <code>useDeferredValue</code> marca um <strong>valor</strong> como menos urgente. A diferença é sutil mas importante: você não controla quando a atualização acontece. O React recebe um novo valor, sabe que pode ser desatualizado, e processa quando tiver tempo.</p>

<p>Isso é perfeito para situações onde você recebe um novo valor (como um prop filtrado ou estado) e quer renderizar com ele, mas não quer bloquear a interface se a renderização for cara. Diferentemente de <code>useTransition</code>, você não precisa envolver nada em uma função callback.</p>

<pre><code class="language-jsx">import { useDeferredValue, useState } from &#039;react&#039;;

function ListComponent({ items }) {

// deferredItems começará com items, mas será atualizado com baixa prioridade

const deferredItems = useDeferredValue(items);

return (

&lt;ul&gt;

{deferredItems.map(item =&gt; (

&lt;li key={item.id}&gt;{item.name}&lt;/li&gt;

))}

&lt;/ul&gt;

);

}

export default function App() {

const [query, setQuery] = useState(&#039;&#039;);

// Filtra imediatamente para manter o input responsivo

const filteredItems = items.filter(item =&gt;

item.name.toLowerCase().includes(query.toLowerCase())

);

return (

&lt;div&gt;

&lt;input

type=&quot;text&quot;

value={query}

onChange={(e) =&gt; setQuery(e.target.value)}

placeholder=&quot;Digite para filtrar...&quot;

/&gt;

&lt;ListComponent items={filteredItems} /&gt;

&lt;/div&gt;

);

}

const items = Array.from({ length: 5000 }, (_, i) =&gt; ({

id: i,

name: Item ${i}

}));</code></pre>

<h3>Diferença prática entre useTransition e useDeferredValue</h3>

<p>A confusão é comum. <code>useTransition</code> é para <strong>ações</strong> — você sabe quando algo pesado vai acontecer e marca com <code>startTransition</code>. <code>useDeferredValue</code> é para <strong>valores</strong> — você recebe um novo valor e quer renderizar com baixa prioridade. Se você controla o setState, use <code>useTransition</code>. Se você recebe um valor como prop e quer deferir a renderização, use <code>useDeferredValue</code>.</p>

<pre><code class="language-jsx">// useTransition: você controla quando a operação pesada começa

function Example1() {

const [query, setQuery] = useState(&#039;&#039;);

const [results, setResults] = useState([]);

const [isPending, startTransition] = useTransition();

const handleSearch = (value) =&gt; {

setQuery(value);

startTransition(() =&gt; {

setResults(expensiveSearch(value));

});

};

return (

&lt;div&gt;

&lt;input onChange={(e) =&gt; handleSearch(e.target.value)} /&gt;

{isPending ? &#039;Buscando...&#039; : ${results.length} resultados}

&lt;/div&gt;

);

}

// useDeferredValue: o valor vem de um prop ou cálculo externo

function Example2({ query }) {

const deferredQuery = useDeferredValue(query);

const results = expensiveSearch(deferredQuery);

return (

&lt;div&gt;

{query !== deferredQuery &amp;&amp; &#039;Atualizando...&#039;}

{results.length} resultados

&lt;/div&gt;

);

}</code></pre>

<p>Note que em Example2, você pode comparar <code>query</code> (atual) com <code>deferredQuery</code> (atrasado) para saber se está desatualizado. Isso é útil para mostrar indicadores visuais.</p>

<h2>Padrões Avançados e Boas Práticas</h2>

<h3>Combinando Suspense, Transitions e useDeferredValue</h3>

<p>As três features trabalham juntas perfeitamente. Aqui está um exemplo completo e realista:</p>

<pre><code class="language-jsx">import {

Suspense,

useTransition,

useDeferredValue,

useState

} from &#039;react&#039;;

// Componente que lança uma Promise (simula fetch)

function SearchResults({ query }) {

const [results] = useState(() =&gt; {

// Simula uma requisição que demora

throw new Promise(resolve =&gt; {

setTimeout(() =&gt; {

resolve([

{ id: 1, title: Resultado para &quot;${query}&quot; },

{ id: 2, title: Outro resultado para &quot;${query}&quot; }

]);

}, 1000);

});

});

return (

&lt;div&gt;

{results.map(r =&gt; (

&lt;div key={r.id}&gt;{r.title}&lt;/div&gt;

))}

&lt;/div&gt;

);

}

function SearchApp() {

const [input, setInput] = useState(&#039;&#039;);

const [isPending, startTransition] = useTransition();

const deferredInput = useDeferredValue(input);

const handleChange = (e) =&gt; {

const value = e.target.value;

setInput(value); // Urgente

startTransition(() =&gt; {

// O setState aqui marca a busca como menos urgente

// O useDeferredValue garante que renderizamos com o valor atrasado

});

};

return (

&lt;div&gt;

&lt;input

value={input}

onChange={handleChange}

placeholder=&quot;Busque algo...&quot;

/&gt;

&lt;Suspense fallback={&lt;div&gt;Carregando resultados...&lt;/div&gt;}&gt;

{deferredInput &amp;&amp; &lt;SearchResults query={deferredInput} /&gt;}

&lt;/Suspense&gt;

{isPending &amp;&amp; &lt;p&gt;Atualizando resultados...&lt;/p&gt;}

&lt;/div&gt;

);

}

export default SearchApp;</code></pre>

<h3>Evitando armadilhas comuns</h3>

<p>A maior armadilha é tentar usar Concurrent Mode com código antigo que não foi preparado para isso. Se você tem <code>useEffect</code> lançando requests sem proper cleanup, Suspense vai quebrar. Certifique-se de usar bibliotecas compatíveis como React Query, SWR, ou Relay.</p>

<p>Outra armadilha é não entender que <code>useDeferredValue</code> retorna um valor potencialmente <strong>desatualizado</strong>. Se você atualiza um filtro e immediamente renderiza baseado no <code>deferredValue</code>, o usuário verá a lista antiga por um breve momento. Isso é proposital — Trade-off entre responsividade e latência. Use indicadores visuais para informar ao usuário que está desatualizado.</p>

<pre><code class="language-jsx">function GoodExample() {

const [input, setInput] = useState(&#039;&#039;);

const deferredInput = useDeferredValue(input);

const isStale = input !== deferredInput;

return (

&lt;div&gt;

&lt;input

value={input}

onChange={(e) =&gt; setInput(e.target.value)}

/&gt;

{isStale &amp;&amp; &lt;div style={{ opacity: 0.5 }}&gt;Aguardando atualização...&lt;/div&gt;}

&lt;Results query={deferredInput} /&gt;

&lt;/div&gt;

);

}</code></pre>

<h2>Conclusão</h2>

<p>Você aprendeu que <strong>Concurrent Mode não é um único recurso, mas um paradigma</strong> onde o React pode interromper e retomar renderizações, priorizando interações de usuário. Suspense simplifica o carregamento de dados inverting o fluxo para lançar Promises, <code>useTransition</code> permite marcar updates como menos urgentes, e <code>useDeferredValue</code> adia a renderização de valores para manter responsividade. A chave é usar a ferramenta correta para o problema: Suspense para dados, Transitions para ações, e useDeferredValue para valores computados custosos. Comece simples, implemente com bibliotecas maduras como React Query, e observe como seu aplicativo fica genuinamente mais responsivo — não por truques visuais, mas por priorização real de trabalho.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://react.dev/reference/react/useTransition" target="_blank" rel="noopener noreferrer">React Concurrent Features - Documentação Oficial</a></li>

<li><a href="https://react.dev/reference/react/Suspense" target="_blank" rel="noopener noreferrer">Suspense for Data Fetching - React Docs</a></li>

<li><a href="https://react.dev/reference/react/useDeferredValue" target="_blank" rel="noopener noreferrer">useDeferredValue - API Reference</a></li>

<li><a href="https://tanstack.com/query/latest/docs/react/suspense" target="_blank" rel="noopener noreferrer">React Query Documentation - Suspense Integration</a></li>

<li><a href="https://github.com/reactwg/react-18/discussions" target="_blank" rel="noopener noreferrer">Concurrent Rendering in React - Deep Dive Article</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em React & Frontend

Controlled vs Uncontrolled Components: Quando Usar Cada Abordagem na Prática
Controlled vs Uncontrolled Components: Quando Usar Cada Abordagem na Prática

O Que São Componentes Controlados e Não Controlados? Antes de mais nada, prec...

Implementando um Mini React do Zero: createElement, render e Hooks: Do Básico ao Avançado
Implementando um Mini React do Zero: createElement, render e Hooks: Do Básico ao Avançado

Entendendo a Arquitetura do React React é uma biblioteca que revolucionou a f...

Monorepo de Componentes React: Storybook, Chromatic e Releases: Do Básico ao Avançado
Monorepo de Componentes React: Storybook, Chromatic e Releases: Do Básico ao Avançado

O Que é um Monorepo de Componentes React Um monorepo (repositório monolítico)...