<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) => {
let status = 'pending';
let result;
const promise = fetch(/api/users/${id})
.then(res => res.json())
.then(data => {
status = 'success';
result = data;
})
.catch(err => {
status = 'error';
result = err;
});
return {
read() {
if (status === 'pending') throw promise;
if (status === 'error') throw result;
return result;
}
};
};
const userResource = fetchUser(1);
function UserProfile() {
const user = userResource.read();
return <div>{user.name}</div>;
}
export default function App() {
return (
<Suspense fallback={<div>Carregando usuário...</div>}>
<UserProfile />
</Suspense>
);
}</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 "loading em cascata" 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 '@tanstack/react-query';
function UserProfile({ userId }) {
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(/api/users/${userId}).then(r => r.json()),
suspense: true,
});
return <div>{user.name}</div>;
}
export default function App() {
return (
<Suspense fallback={<div>Carregando...</div>}>
<UserProfile userId={1} />
</Suspense>
);
}</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 'react';
function SearchUsers() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
// Update do input é urgente
setQuery(value);
// Busca é menos urgente
startTransition(() => {
const filtered = heavyFilter(value);
setResults(filtered);
});
};
return (
<div>
<input
type="text"
value={query}
onChange={handleChange}
placeholder="Digite para buscar..."
/>
{isPending && <span>Buscando...</span>}
<ul>
{results.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
function heavyFilter(query) {
// Simula uma operação pesada
const mockUsers = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: User ${i}
}));
return mockUsers.filter(u =>
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 'react';
import { useNavigate } from 'react-router-dom';
function Navigation() {
const navigate = useNavigate();
const [isPending, startTransition] = useTransition();
const handleNavigation = (path) => {
startTransition(() => {
navigate(path);
});
};
return (
<nav>
<button
onClick={() => handleNavigation('/home')}
disabled={isPending}
>
Home {isPending && '(carregando...)'}
</button>
<button
onClick={() => handleNavigation('/about')}
disabled={isPending}
>
Sobre {isPending && '(carregando...)'}
</button>
</nav>
);
}
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 'react';
function ListComponent({ items }) {
// deferredItems começará com items, mas será atualizado com baixa prioridade
const deferredItems = useDeferredValue(items);
return (
<ul>
{deferredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default function App() {
const [query, setQuery] = useState('');
// Filtra imediatamente para manter o input responsivo
const filteredItems = items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Digite para filtrar..."
/>
<ListComponent items={filteredItems} />
</div>
);
}
const items = Array.from({ length: 5000 }, (_, i) => ({
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('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (value) => {
setQuery(value);
startTransition(() => {
setResults(expensiveSearch(value));
});
};
return (
<div>
<input onChange={(e) => handleSearch(e.target.value)} />
{isPending ? 'Buscando...' : ${results.length} resultados}
</div>
);
}
// useDeferredValue: o valor vem de um prop ou cálculo externo
function Example2({ query }) {
const deferredQuery = useDeferredValue(query);
const results = expensiveSearch(deferredQuery);
return (
<div>
{query !== deferredQuery && 'Atualizando...'}
{results.length} resultados
</div>
);
}</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 'react';
// Componente que lança uma Promise (simula fetch)
function SearchResults({ query }) {
const [results] = useState(() => {
// Simula uma requisição que demora
throw new Promise(resolve => {
setTimeout(() => {
resolve([
{ id: 1, title: Resultado para "${query}" },
{ id: 2, title: Outro resultado para "${query}" }
]);
}, 1000);
});
});
return (
<div>
{results.map(r => (
<div key={r.id}>{r.title}</div>
))}
</div>
);
}
function SearchApp() {
const [input, setInput] = useState('');
const [isPending, startTransition] = useTransition();
const deferredInput = useDeferredValue(input);
const handleChange = (e) => {
const value = e.target.value;
setInput(value); // Urgente
startTransition(() => {
// O setState aqui marca a busca como menos urgente
// O useDeferredValue garante que renderizamos com o valor atrasado
});
};
return (
<div>
<input
value={input}
onChange={handleChange}
placeholder="Busque algo..."
/>
<Suspense fallback={<div>Carregando resultados...</div>}>
{deferredInput && <SearchResults query={deferredInput} />}
</Suspense>
{isPending && <p>Atualizando resultados...</p>}
</div>
);
}
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('');
const deferredInput = useDeferredValue(input);
const isStale = input !== deferredInput;
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
/>
{isStale && <div style={{ opacity: 0.5 }}>Aguardando atualização...</div>}
<Results query={deferredInput} />
</div>
);
}</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><!-- FIM --></p>