Optimistic UI — Resposta Instantânea com Rollback
Atualizar UI antes da resposta do servidor. useOptimistic do React 19, rollback em erro.
Definição
Optimistic UI atualiza a interface imediatamente ao receber a ação do usuário, sem esperar a confirmação do servidor. Se o servidor falhar, a UI faz rollback para o estado anterior. Cria a percepção de resposta instantânea. Usado extensivamente em: likes, follows, reordenar listas (drag-and-drop), send de mensagens, toggle de switches.
Problema → Solução
Problema
UI que congela esperando confirmação do servidor parece lenta mesmo com boa infraestrutura. Botão de 'curtir' que demora 300ms para responder causa má experiência quando a operação é trivial.Solução
useOptimistic (React 19) é a forma canônica. Para React 18: setar estado local imediatamente, fazer o request em paralelo, em caso de erro reverter com setQueryData do React Query ou restaurar estado local. O truque: guardar o estado anterior antes de atualizar.Dica de entrevista
Tip
Nem toda ação deve ser optimistic. Regra: operação é reversível + falha é rara + latência importa → optimistic. Operações financeiras (pagamento), deletar dados permanentemente, ou operações com dependências → esperar confirmação do servidor. O Uber usa optimistic para curtir um motorista mas não para solicitar a corrida.Código
// React 19 com useOptimistic
import { useOptimistic, useTransition } from 'react'
function LikeButton({ postId, initialLikes }: { postId: string; initialLikes: number }) {
const [optimisticLikes, setOptimisticLikes] = useOptimistic(initialLikes)
const [isPending, startTransition] = useTransition()
const handleLike = async () => {
startTransition(async () => {
setOptimisticLikes(prev => prev + 1) // atualiza UI imediatamente
try {
await likePost(postId) // request ao servidor
} catch {
// useOptimistic reverte automaticamente em caso de erro
}
})
}
return (
<button onClick={handleLike} disabled={isPending}>
❤️ {optimisticLikes}
</button>
)
}
// React 18 com React Query — padrão manual
const mutation = useMutation({
mutationFn: (id: string) => likePost(id),
onMutate: async (id) => {
await queryClient.cancelQueries({ queryKey: ['post', id] })
const prev = queryClient.getQueryData(['post', id]) // snapshot
queryClient.setQueryData(['post', id], (old: Post) => ({
...old, likes: old.likes + 1 // optimistic
}))
return { prev } // contexto para rollback
},
onError: (err, id, context) => {
queryClient.setQueryData(['post', id], context?.prev) // rollback
},
onSettled: (_, __, id) => {
queryClient.invalidateQueries({ queryKey: ['post', id] }) // refetch
},
})Perguntas de entrevista
Q.Como implementar rollback confiável?▾
A.Guardar snapshot do estado anterior: `const prev = queryClient.getQueryData(key)`. No catch da mutation: `queryClient.setQueryData(key, prev)`. Com React Query, useMutation tem callbacks onMutate (salva prev, aplica optimistic), onError (rollback), onSettled (invalida query para buscar dado real). O useOptimistic do React 19 faz isso automaticamente.
Q.O que fazer quando o servidor retorna diferente do optimistic?▾
A.Na onSettled ou onSuccess, invalidar a query para refetch do dado real: `queryClient.invalidateQueries({ queryKey: key })`. O dado optimistic é substituído pelo dado real. Se o servidor enriquece o dado (ex: adiciona um campo `id` ao item criado), o optimistic temporário some e o real aparece — pode causar um flash visual. Solução: usar um id temporário (uuid) que é substituído pelo id real.
Q.Optimistic UI e conflito de edições concorrentes (dois usuários editam o mesmo item)?▾
A.Problema clássico de consistency. Abordagens: Last-write-wins (mais simples, pode perder dados), OT (Operational Transformation, Google Docs), CRDT (Conflict-free Replicated Data Types, Figma, Notion). Para maioria dos apps: last-write-wins com timestamp é suficiente. Para colaboração em tempo real em documentos: CRDT com bibliotecas como Yjs ou Automerge.
117/122← →