Hook useOptimistic
Muestra un estado optimista (predicho) mientras una acción asincrónica está en progreso, luego se reconcilia cuando se completa.
Busca en todas las páginas de la documentación
Muestra un estado optimista (predicho) mientras una acción asincrónica está en progreso, luego se reconcilia cuando se completa.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tarjeta de referencia rápida - lista para copiar-pegar.
const [optimisticMessages, addOptimistic] = useOptimistic(
messages,
(currentState, newMessage: string) => [
...currentState,
{ text: newMessage, sending: true },
]
);
// Llama dentro de una acción o transición
startTransition(() => {
addOptimistic("Hello!");
await sendMessage("Hello!");
});Cuándo usarlo: Quieres que la interfaz se actualice instantáneamente cuando un usuario realiza una acción (como enviar un mensaje, dar me gusta a una publicación o agregar un elemento) mientras el servidor procesa la solicitud en el fondo.
"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;
// Simula retraso del servidor
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: "¡Bienvenido al 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">(enviando...)</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="Escribe un mensaje..."
required
/>
<button
type="submit"
disabled={isPending}
className="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
Enviar
</button>
</form>
</div>
);
}Lo que esto demuestra:
useOptimistic toma el estado actual y una función de actualización que describe cómo aplicar un cambio optimistaaddOptimistic(value), React inmediatamente muestra el estado optimista fusionado| Parámetro | Tipo | Descripción |
|---|---|---|
state | T | El valor del estado actual (fuente de verdad) |
updateFn | (currentState: T, optimisticValue: V) => T | Función pura que fusiona el valor optimista en el estado actual |
| Retorno | Tipo | Descripción |
|---|---|---|
optimisticState | T | Estado actual con actualizaciones optimistas aplicadas (igual a state cuando ninguna acción está pendiente) |
addOptimistic | (value: V) => void | Función para desencadenar una actualización optimista |
Botón de me gusta optimista:
const [optimisticLikes, addLike] = useOptimistic(
likes,
(current, _: null) => current + 1
);
async function handleLike() {
startTransition(async () => {
addLike(null);
await likePost(postId);
});
}
return (
<button onClick={handleLike}>
{optimisticLikes} Gustos
</button>
);Alternar todo optimista:
const [optimisticTodos, toggleOptimistic] = useOptimistic(
todos,
(state, toggledId: number) =>
state.map((todo) =>
todo.id === toggledId ? { ...todo, done: !todo.done } : todo
)
);Eliminar optimista:
const [optimisticItems, removeOptimistic] = useOptimistic(
items,
(state, removedId: string) => state.filter((item) => item.id !== removedId)
);// Los tipos genéricos se infieren de los parámetros
const [optimistic, add] = useOptimistic(
messages, // T = Message[]
(state, text: string) => ... // V = string
);
// add: (value: string) => void
// optimistic: Message[]
// Genéricos explícitos cuando sea necesario
const [optimistic, add] = useOptimistic<Todo[], number>(
todos,
(state, toggledId) => state.map(t =>
t.id === toggledId ? { ...t, done: !t.done } : t
)
);Llamar a addOptimistic fuera de una transición - El estado optimista solo funciona correctamente dentro de una acción asincrónica o startTransition. Fuera, la superposición se elimina inmediatamente. Solución: Siempre llama a addOptimistic dentro de startTransition o una acción de formulario.
Mutar state en updateFn - Mutar el array de estado actual (p. ej., state.push(item)) causa errores. Solución: Retorna un nuevo array u objeto desde updateFn.
Sin callback de error - No hay una forma integrada de mostrar un toast de error cuando el estado optimista se revierte. Solución: Maneja errores en tu función de acción y actualiza el estado con un mensaje de error.
Múltiples actualizaciones optimistas rápidas - Cada llamada a addOptimistic se aplica sobre el estado optimista anterior, lo que puede dar resultados inesperados si la función de actualización no es componible. Solución: Diseña tu updateFn para ser idempotente o aditiva.
Estado actual obsoleto - Si el prop de estado actual cambia de una fuente diferente mientras una acción está pendiente, la superposición optimista se recalcula sobre el nuevo estado actual. Solución: Este es generalmente el comportamiento correcto, pero ten cuidado al depurar.
| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
useState con reversión manual | Necesitas lógica de reversión personalizada o manejo de errores | Patrones optimistas simples donde la reversión automática es suficiente |
useTransition con isPending | Solo necesitas un indicador de carga, no un valor optimista | Quieres que la interfaz refleje el resultado esperado inmediatamente |
TanStack Query onMutate | Usas TanStack Query y necesitas actualizaciones optimistas con invalidación de caché | Estás usando acciones de servidor y quieres una solución ligera |
| Deshabilitar e indicador | La simplicidad se prefiere y la latencia es baja | Los usuarios esperan retroalimentación instantánea |
¿Por qué useOptimistic sobre estado manual? useOptimistic se revierte automáticamente en caso de fallo y se fusiona correctamente con el estado actual cuando la acción se completa. Las implementaciones manuales son propensas a errores y verbosas.
startTransition o una acción de formulario para mantener el estado optimista visible.useOptimistic se revierte automáticamente en caso de fallo y se fusiona correctamente cuando el estado actual se actualiza.useOptimistic es menos propensa a errores para patrones comunes como agregar, alternar o eliminar elementos.state.push(item)) causa errores porque React espera actualizaciones inmutables.updateFn.// Incorrecto
(state, newItem) => { state.push(newItem); return state; }
// Correcto
(state, newItem) => [...state, newItem]updateFn sea componible -- cada llamada debe producir un resultado válido cuando se estratifica.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 cuando los usuarios esperan retroalimentación instantánea (mensajes, me gusta, toggles).useOptimistic superponeRevisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥