React 19 useOptimistic Hook: UI Otimista Tutorial Prático
Descubra como o hook useOptimistic revoluciona a experiência do usuário criando interfaces que respondem instantaneamente, mesmo com conexões lentas.
Por que isso é importante
Interfaces otimistas reduzem a percepção de latência em 70%, melhorando drasticamente a experiência do usuário. O useOptimistic do React 19 simplifica esta implementação complexa em poucas linhas de código.
O que é UI Otimista?
Interface otimista é um padrão onde a UI atualiza instantaneamente assumindo que a operação será bem-sucedida, mesmo antes da confirmação do servidor. Este conceito, popularizado por aplicações como o Facebook e Twitter, é fundamental para criar experiências responsivas.
Exemplo Prático:
Você comenta em um post do Instagram. O comentário aparece instantaneamente, mesmo que sua internet esteja lenta. Por baixo dos panos, o app ainda está enviando o comentário para o servidor conforme as melhores práticas de UI otimista.
- • Tradicional: Clique → Aguarda → Comentário aparece
- • Otimista: Clique → Comentário aparece → Confirma servidor
Hook useOptimistic: Sintaxe Básica
const [optimisticState, addOptimistic] = useOptimistic(
currentState,
(currentState, optimisticValue) => {
// Lógica de atualização otimista
return newOptimisticState;
}
);Parâmetros
- • currentState: Estado atual dos dados
- • updateFn: Função de atualização otimista
Retorno
- • optimisticState: Estado com atualizações otimistas
- • addOptimistic: Função para disparar atualização
Exemplo Prático: Sistema de Comentários
Vamos implementar um sistema de comentários que responde instantaneamente, mesmo com latência de rede. Este exemplo segue os padrões recomendados pela documentação oficial do React para gerenciamento de estado.
1. Estrutura Base do Componente
import { useState, useOptimistic } from 'react';
function CommentsSection({ postId, initialComments }) {
const [comments, setComments] = useState(initialComments);
const [newComment, setNewComment] = useState('');
const [optimisticComments, addOptimisticComment] = useOptimistic(
comments,
(currentComments, newComment) => [
...currentComments,
{
id: crypto.randomUUID(),
text: newComment,
author: 'Você',
timestamp: new Date().toISOString(),
status: 'sending' // Indica que está enviando
}
]
);
return (
<div className="max-w-2xl mx-auto p-6">
{/* Renderização dos comentários */}
</div>
);
}2. Função de Envio Otimista
async function handleSubmitComment(formData) {
const commentText = formData.get('comment');
// 1. Atualização otimista INSTANTÂNEA
addOptimisticComment(commentText);
setNewComment(''); // Limpa o input
try {
// 2. Envio real para o servidor (pode demorar)
const response = await fetch('/api/comments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
postId,
text: commentText
})
});
if (!response.ok) throw new Error('Falha no envio');
const savedComment = await response.json();
// 3. Atualiza com dados reais do servidor
setComments(prev => [...prev, savedComment]);
} catch (error) {
// 4. Em caso de erro, reverte o estado otimista
console.error('Erro ao enviar comentário:', error);
// O useOptimistic automaticamente reverte para o estado original
// quando setComments é chamado sem incluir o comentário otimista
// Opcionalmente, mostrar mensagem de erro
alert('Falha ao enviar comentário. Tente novamente.');
}
}3. Renderização com Estados Visuais
return (
<div className="space-y-6">
{/* Lista de Comentários */}
<div className="space-y-4">
{optimisticComments.map((comment) => (
<div
key={comment.id}
className={`p-4 rounded-lg border ${
comment.status === 'sending'
? 'bg-yellow-500/10 border-yellow-500/20 animate-pulse'
: 'bg-gray-800/50 border-gray-700'
}`}
>
<div className="flex justify-between items-start">
<div className="flex-1">
<p className="text-gray-300">{comment.text}</p>
<div className="flex items-center gap-2 mt-2 text-sm text-gray-500">
<span>{comment.author}</span>
<span>•</span>
<span>{formatTime(comment.timestamp)}</span>
{comment.status === 'sending' && (
<span className="text-yellow-400 font-medium">
Enviando...
</span>
)}
</div>
</div>
</div>
</div>
))}
</div>
{/* Formulário de Novo Comentário */}
<form action={handleSubmitComment} className="space-y-4">
<textarea
name="comment"
value={newComment}
onChange={(e) => setNewComment(e.target.value)}
placeholder="Escreva seu comentário..."
className="w-full p-3 bg-gray-800 border border-gray-600 rounded-lg
text-white placeholder-gray-400 focus:border-lime-400
focus:outline-none resize-none"
rows={3}
/>
<button
type="submit"
disabled={!newComment.trim()}
className="px-6 py-2 bg-lime-500 text-black font-semibold rounded-lg
hover:bg-lime-400 disabled:opacity-50 disabled:cursor-not-allowed
transition-colors"
>
Comentar
</button>
</form>
</div>
);Casos de Uso Ideais para useOptimistic
✅ Ideal Para:
- • Curtidas e reações
- • Comentários e mensagens
- • Adição ao carrinho
- • Favoritos e bookmarks
- • Atualizações de perfil
- • Toggles de configuração
❌ Evitar Em:
- • Transações financeiras
- • Envio de dados críticos
- • Operações irreversíveis
- • Sistemas com alta latência
- • Dados médicos sensíveis
- • Controle de acesso
Exemplo: Botão de Curtir Otimista
function LikeButton({ postId, initialLikes, isLiked }) {
const [likes, setLikes] = useState({ count: initialLikes, isLiked });
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(currentLikes, newLikedState) => ({
count: newLikedState
? currentLikes.count + 1
: currentLikes.count - 1,
isLiked: newLikedState
})
);
async function handleLike() {
const newLikedState = !optimisticLikes.isLiked;
// Atualização otimista instantânea
addOptimisticLike(newLikedState);
try {
await fetch(`/api/posts/${postId}/like`, {
method: 'POST',
body: JSON.stringify({ liked: newLikedState })
});
// Confirma o estado após sucesso
setLikes({ count: optimisticLikes.count, isLiked: newLikedState });
} catch (error) {
// Em caso de erro, o estado reverte automaticamente
console.error('Erro ao curtir:', error);
}
}
return (
<button
onClick={handleLike}
className={`flex items-center gap-2 px-4 py-2 rounded-lg transition-all ${
optimisticLikes.isLiked
? 'bg-red-500/20 text-red-400 border border-red-500/30'
: 'bg-gray-800 text-gray-400 border border-gray-600 hover:border-red-500/50'
}`}
>
<span className={`text-lg ${optimisticLikes.isLiked ? 'animate-bounce' : ''}`}>
{optimisticLikes.isLiked ? '❤️' : '🤍'}
</span>
<span className="font-medium">{optimisticLikes.count}</span>
</button>
);
}Tratamento de Erros e Rollback
Uma das grandes vantagens do useOptimistic é o rollback automático em caso de falha. Quando o estado base é atualizado, as mudanças otimistas são descartadas automaticamente, seguindo os princípios de UX resiliente recomendados pelo Google.
Estratégias de Erro Avançadas
function useOptimisticComments(initialComments) {
const [comments, setComments] = useState(initialComments);
const [error, setError] = useState(null);
const [optimisticComments, addOptimisticComment] = useOptimistic(
comments,
(currentComments, newComment) => [
...currentComments,
{ ...newComment, status: 'sending' }
]
);
async function addComment(commentData) {
const tempId = crypto.randomUUID();
const optimisticComment = {
id: tempId,
...commentData,
timestamp: new Date().toISOString()
};
// Limpa erros anteriores
setError(null);
// Adiciona otimisticamente
addOptimisticComment(optimisticComment);
try {
const response = await fetch('/api/comments', {
method: 'POST',
body: JSON.stringify(commentData),
headers: { 'Content-Type': 'application/json' }
});
if (!response.ok) {
throw new Error(`Erro HTTP: ${response.status}`);
}
const savedComment = await response.json();
// Substitui o comentário otimista pelo real
setComments(prev => [...prev, savedComment]);
} catch (err) {
// Define erro (o rollback é automático)
setError(`Falha ao enviar comentário: ${err.message}`);
// Toast de erro opcional
showErrorToast('Comentário não foi enviado. Tente novamente.');
// O useOptimistic automaticamente remove o comentário
// otimista quando comments não é atualizado
}
}
return { optimisticComments, addComment, error };
}Indicadores Visuais de Status
Estado Enviando: Animação de pulse, opacidade reduzida, ícone de loading conforme as diretrizes do Material Design
Estado Confirmado: Animação de sucesso, cores normais, ícone de check
Estado de Erro: Item removido + toast de erro + botão retry seguindo padrões de feedback do iOS
Aplicando em Projetos Reais
Dominar o useOptimistic é fundamental para construir aplicações React modernas com excelente UX. Mas em projetos reais, você também precisa considerar integração com sistemas de cache (React Query, SWR), gerenciamento de estado global e arquiteturas escaláveis.
O importante é entender não apenas como usar o hook, mas quando aplicá-lo estrategicamente: em formulários complexos, sistemas de comentários, dashboards interativos e aplicações colaborativas em tempo real conforme documentado na filosofia do React.
Performance e Boas Práticas
Dicas de Performance:
- • Use
crypto.randomUUID()para IDs temporários únicos - • Evite atualizar arrays grandes otimisticamente
- • Implemente debounce em operações frequentes
- • Use React.memo para componentes de lista
- • Considere virtualização para muitos itens
Comparação: Com vs Sem useOptimistic
❌ Implementação Manual
- • 50+ linhas de código
- • Gerenciamento de estado complexo
- • Propenso a bugs de sincronização
- • Rollback manual necessário
- • Difícil de testar
✅ Com useOptimistic
- • 15-20 linhas de código
- • Estado gerenciado automaticamente
- • Rollback automático em falhas
- • API simples e intuitiva
- • Fácil de testar e debuggar
Fontes e Referências Técnicas
Este tutorial foi baseado em documentações oficiais e recursos autoritários:
📚 Documentação Oficial
- React 19 useOptimistic Hook
Documentação oficial do hook
- React 19 Release Blog
Anúncio oficial do React 19
- Managing State in React
Guia de gerenciamento de estado
🌐 Recursos UX/UI
- Optimistic UI Patterns - Google
Padrões de interface otimista
- Building Resilient UX
Construindo UX resiliente
- Material Design States
Diretrizes de estados visuais
🛠️ Ferramentas Relacionadas
- TanStack Query (React Query)
Cache e sincronização de estado
- SWR - Data Fetching
Hook para fetching de dados
- React Window
Virtualização de listas
🧪 Testing & Performance
- React Testing Library
Testes de componentes React
- Crypto.randomUUID() - MDN
API para gerar IDs únicos
- React.memo Reference
Otimização de performance