Hook useOptimistic
Mostra um estado otimista (previsto) enquanto uma ação assíncrona está em andamento, e depois reconcilia quando ela é concluída.
Busque em todas as páginas da documentação
Mostra um estado otimista (previsto) enquanto uma ação assíncrona está em andamento, e depois reconcilia quando ela é concluída.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
const [optimisticMessages, addOptimistic] = useOptimistic(
messages,
(currentState, newMessage: string) => [
...currentState,
{ text: newMessage, sending: true },
]
);
// Call inside an action or transition
startTransition(() => {
addOptimistic("Hello!");
await sendMessage("Hello!");
});Quando usar isso: Você quer que a interface do usuário seja atualizada instantaneamente quando um usuário realiza uma ação (como enviar uma mensagem, curtir uma postagem ou adicionar um item) enquanto o servidor processa a solicitação em segundo plano.
"use client";
import { useOptimistic, useActionState, useRef } from "react";
interface Message {
id: number;
text: string;
sending?: boolean;
}
async function sendMessageAction(
prevState: Message[],
formData: FormData
): Promise<Message[]> {
const text = formData.get("message") as string;
// Simulate server delay
await new Promise((resolve) => setTimeout(resolve, 1500));
return [
...prevState,
{ id: Date.now(), text, sending: false },
];
}
export function Chat() {
const [messages, formAction, isPending] = useActionState(sendMessageAction, [
{ id: 1, text: "Welcome to the chat!", sending: false },
]);
const [optimisticMessages, addOptimistic] = useOptimistic(
messages,
(state, newMessage: string) => [
...state,
{ id: Date.now(), text: newMessage, sending: true },
]
);
const formRef = useRef<HTMLFormElement>(null);
async function handleSubmit(formData: FormData) {
const text = formData.get("message") as string;
if (!text.trim()) return;
formRef.current?.reset();
addOptimistic(text);
await formAction(formData);
}
return (
<div className="space-y-3 max-w-sm">
<ul className="space-y-2">
{optimisticMessages.map((msg) => (
<li
key={msg.id}
className={`text-sm px-3 py-2 rounded ${
msg.sending
? "bg-gray-100 text-gray-400 italic"
: "bg-blue-50 text-gray-900"
}`}
>
{msg.text}
{msg.sending && <span className="ml-2 text-xs">(sending...)</span>}
</li>
))}
</ul>
<form ref={formRef} action={handleSubmit} className="flex gap-2">
<input
name="message"
className="flex-1 border rounded px-3 py-2"
placeholder="Type a message..."
required
/>
<button
type="submit"
disabled={isPending}
className="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
Send
</button>
</form>
</div>
);
}O que isso demonstra:
useOptimistic recebe o estado real e uma função de atualização que descreve como aplicar uma mudança otimista.addOptimistic(valor), o React imediatamente mostra o estado otimista mesclado.| Parâmetro | Tipo | Descrição |
|---|---|---|
state | T | O valor de estado real (fonte da verdade) |
updateFn | (currentState: T, optimisticValue: V) => T | Função pura que mescla o valor otimista no estado atual |
| Retorno | Tipo | Descrição |
|---|---|---|
optimisticState | T | Estado atual com atualizações otimistas aplicadas (igual a state quando nenhuma ação está pendente) |
addOptimistic | (value: V) => void | Função para acionar uma atualização otimista |
Botão de curtir otimista:
const [optimisticLikes, addLike] = useOptimistic(
likes,
(current, _: null) => current + 1
);
async function handleLike() {
startTransition(async () => {
addLike(null);
await likePost(postId);
});
}
return (
<button onClick={handleLike}>
{optimisticLikes} Likes
</button>
);Alternância otimista de tarefa:
const [optimisticTodos, toggleOptimistic] = useOptimistic(
todos,
(state, toggledId: number) =>
state.map((todo) =>
todo.id === toggledId ? { ...todo, done: !todo.done } : todo
)
);Exclusão otimista:
const [optimisticItems, removeOptimistic] = useOptimistic(
items,
(state, removedId: string) => state.filter((item) => item.id !== removedId)
);// The generic types are inferred from parameters
const [optimistic, add] = useOptimistic(
messages, // T = Message[]
(state, text: string) => ... // V = string
);
// add: (value: string) => void
// optimistic: Message[]
// Explicit generics when needed
const [optimistic, add] = useOptimistic<Todo[], number>(
todos,
(state, toggledId) => state.map(t =>
t.id === toggledId ? { ...t, done: !t.done } : t
)
);Chamar addOptimistic fora de uma transição - O estado otimista só funciona corretamente dentro de uma ação assíncrona ou startTransition. Fora disso, a sobreposição é removida imediatamente. Correção: Sempre chame addOptimistic dentro de startTransition ou de uma ação de formulário.
Mutar estado em updateFn - Mutar o array de estado atual (por exemplo, state.push(item)) causa bugs. Correção: Retorne um novo array ou objeto de updateFn.
Sem callback de erro - Não há uma maneira integrada de mostrar um toast de erro quando o estado otimista reverte. Correção: Trate os erros na sua função de ação e atualize o estado com uma mensagem de erro.
Múltiplas atualizações otimistas rápidas - Cada chamada para addOptimistic é aplicada sobre o estado otimista anterior, o que pode levar a resultados inesperados se a função de atualização não for composível. Correção: Projete sua updateFn para ser idempotente ou aditiva.
Estado real obsoleto - Se o prop de estado real mudar de outra fonte enquanto uma ação estiver pendente, a sobreposição otimista recalcula sobre o novo estado real. Correção: Este é geralmente o comportamento correto, mas esteja ciente disso ao depurar.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
useState com rollback manual | Você precisa de lógica de rollback ou tratamento de erros personalizado | Padrões otimistas simples onde o rollback automático é suficiente |
useTransition com isPending | Você só precisa de um indicador de carregamento, não de um valor otimista | Você quer que a interface do usuário reflita o resultado esperado imediatamente |
TanStack Query onMutate | Você usa TanStack Query e precisa de atualizações otimistas com invalidação de cache | Você está usando ações de servidor e quer uma solução leve |
| Desabilitar e spinner | A simplicidade é preferida e a latência é baixa | Os usuários esperam feedback instantâneo |
Por que usar useOptimistic em vez de estado manual? useOptimistic reverte automaticamente em caso de falha e mescla corretamente com o estado real quando a ação é concluída. Implementações manuais são propensas a erros e verbosas.
startTransition ou uma ação de formulário para manter o estado otimista visível.useOptimistic reverte automaticamente em caso de falha e mescla corretamente quando o estado real é atualizado.useOptimistic é menos propenso a erros para padrões comuns como adicionar, alternar ou excluir itens.state.push(item)) causa bugs porque o React espera atualizações imutáveis.updateFn.// Wrong
(state, newItem) => { state.push(newItem); return state; }
// Correct
(state, newItem) => [...state, newItem]updateFn seja composível - cada chamada deve produzir um resultado válido quando empilhada.const [optimistic, add] = useOptimistic(
messages, // T = Message[]
(state, text: string) => [ // V = string
...state,
{ id: Date.now(), text, sending: true },
]
);
// add: (value: string) => void
// optimistic: Message[]const [optimisticLikes, addLike] = useOptimistic(
likes,
(current, _: null) => current + 1
);
async function handleLike() {
startTransition(async () => {
addLike(null);
await likePost(postId);
});
}useOptimistic quando os usuários esperam feedback instantâneo (mensagens, curtidas, alternâncias).useOptimistic sobrepõeRevisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥