🚀 Oferta especial: 60% OFF no CrazyStack - Últimas vagas!Garantir vaga →
React

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.

CrazyStack Team
16 min de leitura
React 19useOptimisticUI OtimistaHooks

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 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.

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.

  • 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.

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.

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

1

Estado Enviando: Animação de pulse, opacidade reduzida, ícone de loading

2

Estado Confirmado: Animação de sucesso, cores normais, ícone de check

3

Estado de Erro: Item removido + toast de erro + botão retry

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

Checklist: Implementação useOptimistic

Hook useOptimistic configurado corretamente
Estados visuais para "enviando" implementados
Tratamento de erro com rollback automático
IDs únicos para itens otimistas
Feedback visual diferenciado por status
Casos de erro comunicados ao usuário
Performance otimizada para listas
Testes unitários implementados

Domine React 19 na Prática

Aprenda todos os recursos avançados do React 19 com projetos reais