useOptimistic é um hook do React 19 que permite à tela mostrar o resultado de uma action antes de ela terminar. Você define um valor otimista dentro de uma action; o React o mostra enquanto a action está pendente e depois volta ao estado real. Se a requisição falhou, o estado real nunca mudou, então a tela se reverte sozinha.
A API falsa deste exemplo faz o papel de uma requisição real e falha a cada terceira chamada, para você ver os dois resultados.
Clique no botão três vezes, esperando um segundo entre os cliques. Os dois primeiros mudam na hora e a linha do valor salvo alcança um segundo depois. O terceiro também muda na hora e depois volta quando a requisição falha. Troque calls % 3 por calls % 2 e cada segundo clique falha.
A sintaxe
const [optimisticState, setOptimistic] = useOptimistic(state, updateFn?);
stateé o valor real, normalmente vindo deuseState, de props ou deuseActionState. Quando nenhuma action está pendente,optimisticStateé exatamente esse valor.setOptimistic(value)define o valor otimista enquanto a action atual executar.updateFné opcional:(currentState, optimisticValue) => nextState. Com ela,setOptimisticrecebe uma mudança em vez de um valor novo inteiro. Veja a forma com reducer abaixo.
O ciclo de um clique é sempre o mesmo:
- Dentro de uma action, você chama
setOptimistic. A tela atualiza na hora. - A action aguarda a requisição.
- Em caso de sucesso, você atualiza o estado real (dentro de
startTransitionquando isso vem depois de umawait). - A action termina.
optimisticStatevolta a acompanharstate: o valor salvo em caso de sucesso, o valor antigo em caso de falha.
O passo 4 é o motivo de não haver código de reversão no exemplo. O bloco catch só define uma mensagem de erro.
Ele precisa executar dentro de uma action
O valor otimista só existe enquanto uma transição está pendente, então setOptimistic tem que ser chamado dentro de uma. Tudo isto conta:
- a função que você passa para
startTransition(ou para ostartTransitiondouseTransition), - uma função passada para
<form action={...}>ou<button formAction={...}>, - a action que você entrega ao useActionState.
Chamado de um onClick comum, o valor otimista não tem uma action em que viver. O React o reverte na hora e, em desenvolvimento, registra "An optimistic state update occurred outside a transition or action".
Atualizações do estado real que acontecem depois de um await devem ser envolvidas no próprio startTransition, como startTransition(() => setLiked(next)) acima. Depois de um await, o React não consegue saber que você ainda está dentro da transição anterior.
Uma lista de mensagens com a forma de reducer
Quando o estado otimista é uma lista, passe uma função de atualização como segundo argumento. O setter então recebe o novo item, e o React o adiciona à lista real como ela estiver naquele momento.
Envie três mensagens com textos diferentes, uma de cada vez. Cada uma aparece apagada com "(sending...)" e fica nítida quando é entregue; a terceira some do chat e aparece como "Not sent". Envie-as rápido e as três ficam apagadas até a última requisição terminar, porque o React mantém os valores otimistas até todas as actions pendentes terminarem. A form action já é uma transição, então aqui addOptimistic não precisa de um startTransition próprio.
Dê a cada item otimista uma key que não colida com os itens reais. O exemplo usa pending- mais o texto, então enviar o mesmo texto duas vezes com as duas pendentes causaria uma colisão; um app real criaria um id no cliente.
Várias atualizações ao mesmo tempo
Como a função de atualização recebe o estado atual, as atualizações otimistas se empilham. Clique rápido e cada clique pendente é aplicado em cima do anterior.
Clique quatro vezes rápido. O carrinho mostra 4 na hora. Quando as requisições terminam, a contagem se acomoda em 3, porque a terceira requisição falhou, e o console avisa. O React mantém os valores otimistas até todas as actions pendentes terminarem e então mostra a contagem real.
Quando usar, e quando não
Atualizações otimistas combinam com ações que quase sempre dão certo e são fáceis de desfazer: curtidas, estrelas, toggles, renomear, adicionar uma mensagem. No caso comum, o usuário não vê spinner nenhum.
Evite-as onde um falso "pronto" enganaria: pagamentos, excluir uma conta, qualquer coisa sobre a qual o usuário possa agir antes de saber o resultado. Nesses casos, mostre um estado pendente com o isPending do useActionState ou do useTransition e espere a resposta real.
Sempre avise o usuário quando acontecer uma reversão. Um valor que volta atrás em silêncio parece um bug. Os dois exemplos acima guardam uma mensagem de erro em estado comum para que ela sobreviva à action.
Com useActionState
useOptimistic e useActionState se encaixam. A action do useActionState já é uma transição, então você pode definir o valor otimista no início dela, e o estado retornado é o valor real para o qual o otimista volta.
const [state, formAction] = useActionState(async (previous, formData) => {
const title = formData.get('title');
setOptimisticTitle(title);
const saved = await saveTitle(title);
return { title: saved.title };
}, { title: 'Untitled' });
const [optimisticTitle, setOptimisticTitle] = useOptimistic(state.title);
Enquanto o salvamento executa, a página mostra o novo título. Quando ele resolve, state.title guarda o valor salvo. Se saveTitle lançar um erro, o erro da action vai para o error boundary mais próximo; para mostrar o título antigo com uma mensagem, capture o erro dentro da action e retorne o estado anterior mais um erro.
Erros comuns
Chamar o setter fora de uma action. O valor otimista não fica na tela, e o React avisa em desenvolvimento. Envolva-o em startTransition ou leve-o para uma form action.
Esquecer de atualizar o estado real. Em caso de sucesso, a action precisa mudar o estado que o useOptimistic espelha (setLiked, setMessages). Caso contrário, o valor otimista desaparece quando a action termina, e uma requisição bem-sucedida parece uma reversão.
Atualizar o estado real depois de um await sem transição. A atualização ainda acontece, mas o React pode mostrá-la em um momento diferente do fim da action. Envolva-a em startTransition, como os três exemplos fazem.
Usar o valor otimista como fonte da verdade. Envie requisições e calcule totais a partir do estado real. O valor otimista é só para exibição e pode ser descartado a qualquer momento.
Perguntas frequentes
O que o useOptimistic faz?
Ele dá a você uma cópia de uma parte do estado que você pode mudar na hora enquanto uma action assíncrona executa. Quando a action termina, a cópia volta a acompanhar o estado real, que a essa altura guarda ou o resultado salvo ou o valor antigo.
Como o useOptimistic reverte em caso de erro?
Ele não precisa de um caminho especial para erros. O valor otimista só existe enquanto a action está pendente. Se a requisição falha e você nunca atualiza o estado real, o hook mostra o estado real de novo, que é o valor de antes do clique.
Por que aparece "An optimistic state update occurred outside a transition or action"?
O setter foi chamado de um event handler comum. Chame-o dentro de uma função passada para startTransition, dentro de um <form action> ou dentro de uma action do useActionState.
Qual é o segundo argumento do useOptimistic?
Uma função de atualização opcional, (currentState, optimisticValue) => newState, como um reducer. Com ela, o setter recebe só a mudança (uma nova mensagem, +1) e o React calcula o estado otimista, mesmo quando várias atualizações estão pendentes ao mesmo tempo.
O useOptimistic é só para formulários?
Não. Ele funciona em qualquer transição, então um botão que chama startTransition(async () => { ... }) pode usá-lo tanto quanto uma form action.