<h2>Introdução ao Problema de Performance em React</h2>
<p>Quando construímos aplicações web modernas, uma das maiores reclamações dos usuários não é sobre funcionalidade, mas sobre <strong>responsividade</strong>. Você já clicou em um botão e sentiu aquele lag frustrante? Aquele atraso entre a ação e a reação visual? Isso acontece porque o React precisa processar a atualização de estado, renderizar novos componentes e atualizar o DOM — tudo isso acontece no mesmo thread que executa JavaScript, CSS e responde a eventos do usuário.</p>
<p>O React 18 introduziu dois hooks revolucionários para resolver esse problema de forma elegante: <code>useTransition</code> e <code>useOptimistic</code>. Eles permitem que você marque atualizações como "não urgentes", liberando o thread principal para responder a interações críticas como cliques e digitação. Neste artigo, você aprenderá não apenas como usá-los, mas <strong>por que funcionam</strong> e quando realmente faz diferença.</p>
<h2>useTransition: Priorização de Atualizações de Estado</h2>
<h3>O Conceito Fundamental</h3>
<p><code>useTransition</code> permite que você marque uma atualização de estado como uma <strong>transição</strong> — uma operação que pode ser interrompida e resumida sem prejudicar a experiência do usuário. Ao invés de travar o navegador enquanto processa algo pesado, React pausa o trabalho, processa eventos do usuário que chegam, e depois continua.</p>
<p>O hook retorna dois valores: uma função <code>startTransition</code> que você chama com código que atualiza estado, e um booleano <code>isPending</code> que indica se a transição está em progresso. Isso permite que você mostre feedback visual enquanto o React trabalha nos bastidores.</p>
<h3>Exemplo Prático: Filtro de Lista Pesada</h3>
<p>Imagine uma aplicação que filtra uma lista de 10 mil itens enquanto o usuário digita. Sem <code>useTransition</code>, a interface congela. Com ele, a digitação permanece responsiva:</p>
<pre><code class="language-jsx">import { useState, useTransition } from 'react';
export function FilterableList() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: Item ${i},
description: Description for item ${i}
}));
const filteredItems = items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
const handleInputChange = (e) => {
const value = e.target.value;
// Atualização urgente: o input responde imediatamente
setQuery(value);
// Atualização não urgente: filtragem acontece em background
startTransition(() => {
// Aqui você poderia atualizar outro state
// que depende da filtragem pesada
});
};
return (
<div>
<input
type="text"
value={query}
onChange={handleInputChange}
placeholder="Digite para filtrar..."
/>
{isPending && <p>Filtrando...</p>}
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}</code></pre>
<p>Observe que <code>setQuery(value)</code> acontece <strong>fora</strong> de <code>startTransition</code>. Isso é intencional — queremos que o input responda imediatamente ao usuário. Se você quiser, pode colocar a filtragem em um estado separado dentro de <code>startTransition</code>, mas neste caso simples, o React otimiza automaticamente.</p>
<h3>Padrão Avançado: Múltiplos Estados com Transição</h3>
<p>Quando você tem lógica mais complexa, vale a pena separar claramente qual estado é urgente e qual não:</p>
<pre><code class="language-jsx">import { useState, useTransition } from 'react';
export function SearchWithResults() {
const [inputValue, setInputValue] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (value) => {
// Urgente: atualiza o input imediatamente
setInputValue(value);
// Não urgente: calcula e renderiza resultados em background
startTransition(() => {
// Simulando busca pesada (em produção, seria uma API call)
const filtered = simulateExpensiveSearch(value);
setResults(filtered);
});
};
return (
<div>
<input
value={inputValue}
onChange={(e) => handleSearch(e.target.value)}
placeholder="Buscar..."
/>
{isPending && <div className="spinner">Carregando resultados...</div>}
<ul className={isPending ? 'opacity-50' : ''}>
{results.map(result => (
<li key={result.id}>{result.title}</li>
))}
</ul>
</div>
);
}
function simulateExpensiveSearch(query) {
// Simulação de operação pesada
const start = performance.now();
while (performance.now() - start < 500) {}
return Array.from({ length: 50 }, (_, i) => ({
id: i,
title: Resultado: ${query} - ${i}
}));
}</code></pre>
<h2>useOptimistic: Feedback Imediato com Sincronização Segura</h2>
<h3>Quando Você Precisa Adivinhar o Futuro</h3>
<p><code>useOptimistic</code> resolve um problema diferente: quando você envia uma ação para o servidor (POST, PUT, DELETE), o usuário quer <strong>ver o resultado imediatamente</strong>, mas você só terá a confirmação em alguns milissegundos ou segundos. Se você esperar a resposta do servidor, a interface parece lenta. Se você atualizar o estado antes da confirmação e a requisição falhar, fica confuso.</p>
<p>A solução é <strong>atualizar o estado otimisticamente</strong> — mostrar o resultado esperado enquanto a requisição está em progresso, e fazer rollback se falhar. <code>useOptimistic</code> torna isso seguro e simples.</p>
<h3>Exemplo Prático: Like Button</h3>
<p>Vamos implementar um botão de like que atualiza a contagem imediatamente, mesmo esperando a resposta do servidor:</p>
<pre><code class="language-jsx">import { useOptimistic, useState } from 'react';
export function PostWithLike() {
const [post, setPost] = useState({
id: 1,
title: 'Meu Primeiro Post',
likes: 42,
liked: false
});
const [optimisticPost, addOptimisticLike] = useOptimistic(
post,
(currentPost, newLikes) => ({
...currentPost,
likes: newLikes,
liked: !currentPost.liked
})
);
const handleLike = async () => {
// Atualiza otimisticamente
addOptimisticLike(optimisticPost.liked ? post.likes - 1 : post.likes + 1);
try {
// Envia para o servidor
const response = await fetch(/api/posts/${post.id}/like, {
method: 'POST'
});
const updatedPost = await response.json();
// Sincroniza com a resposta real do servidor
setPost(updatedPost);
} catch (error) {
// Se falhar, o estado otimístico é descartado automaticamente
// e volta ao valor original
console.error('Erro ao fazer like:', error);
}
};
return (
<div className="post">
<h2>{optimisticPost.title}</h2>
<button
onClick={handleLike}
className={optimisticPost.liked ? 'liked' : ''}
>
❤️ {optimisticPost.likes}
</button>
</div>
);
}</code></pre>
<p>Veja como funciona:</p>
<ol>
<li>Você clica no botão</li>
<li>O estado muda <strong>imediatamente</strong> — o contador aumenta e a cor muda</li>
<li>Em paralelo, a requisição HTTP é enviada</li>
<li>Se a resposta vier com sucesso, <code>setPost</code> sincroniza o estado real</li>
<li>Se falhar, o React <strong>automaticamente</strong> reverte para o estado anterior sem você fazer nada</li>
</ol>
<h3>Padrão Avançado: Múltiplas Ações Otimistas</h3>
<p>Em aplicações reais, você pode ter múltiplas ações pendentes. <code>useOptimistic</code> permite atualizações em fila:</p>
<pre><code class="language-jsx">import { useOptimistic, useState } from 'react';
export function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, title: 'Aprender React 18', completed: false }
]);
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, newTodo]
);
const handleAddTodo = async (title) => {
const tempTodo = {
id: Date.now(),
title,
completed: false
};
// Mostra o novo todo imediatamente
addOptimisticTodo(tempTodo);
try {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title })
});
const createdTodo = await response.json();
// Substitui o otimista pelo real (com ID real do servidor)
setTodos(current =>
current.map(t => t.id === tempTodo.id ? createdTodo : t)
);
} catch (error) {
console.error('Erro ao criar todo:', error);
// Automaticamente reverte
}
};
return (
<div>
<button onClick={() => handleAddTodo('Novo Todo')}>
Adicionar
</button>
<ul>
{optimisticTodos.map(todo => (
<li key={todo.id} className={todo.completed ? 'completed' : ''}>
{todo.title}
</li>
))}
</ul>
</div>
);
}</code></pre>
<h2>Combinando Ambos os Hooks: O Padrão Completo</h2>
<h3>Quando Usar Cada Um</h3>
<p><code>useTransition</code> é para <strong>atualizações de estado derivadas</strong> — quando mudar um estado causa cálculos pesados. <code>useOptimistic</code> é para <strong>interações com servidor</strong> — quando você quer mostrar resultado antes da confirmação. Frequentemente, você usa ambos na mesma aplicação, em contextos diferentes.</p>
<p>Mas existe um caso de uso poderoso onde eles trabalham juntos: <strong>formulários com validação assíncrona pesada</strong>. Você mostra feedback imediato (otimista) enquanto valida no servidor em background (transição).</p>
<h3>Exemplo Prático: Formulário de Reserva</h3>
<pre><code class="language-jsx">import { useState, useTransition, useOptimistic } from 'react';
export function BookingForm() {
const [booking, setBooking] = useState({
date: '',
time: '',
guests: 1,
status: 'idle' // idle, submitting, success, error
});
const [optimisticBooking, addOptimisticBooking] = useOptimistic(
booking,
(current, updates) => ({ ...current, ...updates })
);
const [isPending, startTransition] = useTransition();
const handleSubmit = async (e) => {
e.preventDefault();
// Atualiza otimisticamente com status de submissão
addOptimisticBooking({ status: 'submitting' });
// Processamento em background (pode ser pesado)
startTransition(async () => {
try {
const response = await fetch('/api/bookings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(booking)
});
if (response.ok) {
const result = await response.json();
setBooking(prev => ({
...prev,
status: 'success',
...result
}));
} else {
setBooking(prev => ({
...prev,
status: 'error'
}));
}
} catch (error) {
setBooking(prev => ({
...prev,
status: 'error'
}));
}
});
};
const handleChange = (e) => {
const { name, value } = e.target;
setBooking(prev => ({
...prev,
[name]: value
}));
};
return (
<form onSubmit={handleSubmit}>
<input
type="date"
name="date"
value={optimisticBooking.date}
onChange={handleChange}
disabled={optimisticBooking.status === 'submitting'}
/>
<input
type="time"
name="time"
value={optimisticBooking.time}
onChange={handleChange}
disabled={optimisticBooking.status === 'submitting'}
/>
<input
type="number"
name="guests"
min="1"
max="10"
value={optimisticBooking.guests}
onChange={handleChange}
disabled={optimisticBooking.status === 'submitting'}
/>
<button type="submit" disabled={optimisticBooking.status === 'submitting'}>
{optimisticBooking.status === 'submitting' ? 'Reservando...' : 'Reservar'}
</button>
{optimisticBooking.status === 'success' && (
<p className="success">Reserva confirmada!</p>
)}
{optimisticBooking.status === 'error' && (
<p className="error">Erro ao fazer reserva. Tente novamente.</p>
)}
{isPending && (
<p className="info">Processando reserva...</p>
)}
</form>
);
}</code></pre>
<p>Neste exemplo:</p>
<ol>
<li>O usuário submete o formulário</li>
<li>Imediatamente, o estado é atualizado otimisticamente com <code>status: 'submitting'</code></li>
<li>O botão desabilita e mostra "Reservando..."</li>
<li>A requisição HTTP é enviada dentro de <code>startTransition</code></li>
<li>Se suceder, <code>setBooking</code> atualiza com a resposta real</li>
<li>Se falhar, o estado reverte e mostra erro</li>
</ol>
<h2>Otimizações Práticas e Armadilhas Comuns</h2>
<h3>O Perigo do Uso Incorreto</h3>
<p>Um erro comum é colocar <strong>toda</strong> a lógica dentro de <code>startTransition</code>. Lembre-se: queremos apenas adiar o que é não-urgente. Se você colocar <code>setInput</code> dentro de <code>startTransition</code>, o input lag volta.</p>
<pre><code class="language-jsx"></code></pre>
<h3>Medindo o Impacto Real</h3>
<p>Para validar se você realmente precisa dessas otimizações, use as DevTools do React ou o Performance API nativo:</p>
<pre><code class="language-jsx">export function PerformanceMonitor({ children }) {
return (
<>
<React.Profiler
id="main"
onRender={(id, phase, actualDuration) => {
if (actualDuration > 16) { // ~60fps threshold
console.warn(
Render lento em ${id}: ${actualDuration.toFixed(2)}ms
);
}
}}
>
{children}
</React.Profiler>
</>
);
}</code></pre>
<h3>Compatibilidade com Server Components (React 19)</h3>
<p>Se você estiver usando React 19 com Server Components, <code>useOptimistic</code> funciona em Client Components e pode trabalhar com Server Actions:</p>
<pre><code class="language-jsx">'use client';
import { useOptimistic } from 'react';
export function ClientComponent({ updateServerData }) {
const [optimisticData, addOptimisticUpdate] = useOptimistic(
null,
(_, newValue) => newValue
);
const handleClick = async () => {
addOptimisticUpdate('Loading...');
await updateServerData();
};
return (
<button onClick={handleClick}>
{optimisticData || 'Click'}
</button>
);
}</code></pre>
<h2>Conclusão</h2>
<p>Aprendemos que <code>useTransition</code> e <code>useOptimistic</code> são ferramentas fundamentais para construir aplicações React modernas com excelente experiência do usuário. <code>useTransition</code> resolve o problema de <strong>renderizações pesadas</strong> priorizando atualizações urgentes, enquanto <code>useOptimistic</code> resolve o problema de <strong>latência de rede</strong> mostrando feedback imediato sem sacrificar a segurança dos dados.</p>
<p>O aprendizado prático mais importante é <strong>simplicidade</strong>: não comece a usar esses hooks por usar. Meça se há realmente problemas de performance, identifique a causa (renderização ou requisição), e aplique a solução apropriada. Um <code>useTransition</code> bem colocado pode transformar uma interface que parecia ruim em excelente, com apenas 3 linhas de código.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://react.dev/reference/react/useTransition" target="_blank" rel="noopener noreferrer">React 18 Hooks Documentation - useTransition</a></li>
<li><a href="https://react.dev/reference/react/useOptimistic" target="_blank" rel="noopener noreferrer">React 18 Hooks Documentation - useOptimistic</a></li>
<li><a href="https://react.dev/blog/2022/03/29/react-v18" target="_blank" rel="noopener noreferrer">React 18 Concurrent Features Guide</a></li>
<li><a href="https://web.dev/vitals/" target="_blank" rel="noopener noreferrer">Web Vitals: Core Web Vitals Explained</a></li>
<li><a href="https://react.dev/learn/render-and-commit" target="_blank" rel="noopener noreferrer">React Performance Optimization Patterns</a></li>
</ul>
<p><!-- FIM --></p>