useOptimistic es un hook de React 19 que permite que la pantalla muestre el resultado de una acción antes de que la acción termine. Defines un valor optimista dentro de una acción; React lo muestra mientras la acción está pendiente y luego vuelve al estado real. Si la petición falló, el estado real nunca cambió, así que la pantalla se revierte sola.
La API falsa de este ejemplo sustituye a una petición real y falla en cada tercera llamada, para que veas ambos resultados.
Haz clic en el botón tres veces, esperando un segundo entre clics. Los dos primeros cambian al instante y la línea de lo guardado se pone al día un segundo después. El tercero también cambia al instante, y luego vuelve atrás cuando la petición falla. Cambia calls % 3 por calls % 2 y falla cada segundo clic.
La sintaxis
const [optimisticState, setOptimistic] = useOptimistic(state, updateFn?);
statees el valor real, normalmente deuseState, de las props o deuseActionState. Cuando no hay ninguna acción pendiente,optimisticStatees exactamente este valor.setOptimistic(value)define el valor optimista mientras dure la acción actual.updateFnes opcional:(currentState, optimisticValue) => nextState. Con ella,setOptimisticrecibe un cambio en lugar de un valor nuevo completo. Mira la forma con reducer más abajo.
El ciclo de vida de un clic siempre es el mismo:
- Dentro de una acción, llamas a
setOptimistic. La pantalla se actualiza al instante. - La acción espera la petición.
- Si tiene éxito, actualizas el estado real (dentro de
startTransitioncuando viene después de unawait). - La acción termina.
optimisticStatevuelve a seguir astate: el valor guardado si tuvo éxito, el valor viejo si falló.
El paso 4 es la razón por la que no hay código de reversión en el ejemplo. El bloque catch solo define un mensaje de error.
Debe ejecutarse dentro de una acción
El valor optimista solo existe mientras una transición está pendiente, así que setOptimistic tiene que llamarse dentro de una. Todas estas cuentan:
- la función que pasas a
startTransition(o elstartTransitiondeuseTransition), - una función pasada a
<form action={...}>o<button formAction={...}>, - la acción que le das a useActionState.
Si lo llamas desde un onClick normal, el valor optimista no tiene ninguna acción en la que vivir. React lo revierte enseguida, y en desarrollo registra "An optimistic state update occurred outside a transition or action".
Las actualizaciones del estado real que ocurren después de un await deben ir envueltas en su propio startTransition, como startTransition(() => setLiked(next)) arriba. Después de un await, React no puede saber que sigues dentro de la transición anterior.
Una lista de mensajes con la forma de reducer
Cuando el estado optimista es una lista, pasa una función de actualización como segundo argumento. El setter recibe entonces el nuevo elemento, y React lo agrega a la lista real que haya en ese momento.
Envía tres mensajes con textos distintos, uno a la vez. Cada uno aparece atenuado con "(sending...)" y se vuelve sólido cuando se entrega; el tercero desaparece del chat y aparece como "Not sent". Envíalos rápido y los tres se quedan atenuados hasta que termina la última petición, porque React conserva los valores optimistas hasta que todas las acciones pendientes terminaron. La acción del formulario ya es una transición, así que aquí addOptimistic no necesita su propio startTransition.
Dale a cada elemento optimista una key que no choque con los elementos reales. El ejemplo usa pending- más el texto, así que enviar el mismo texto dos veces mientras ambos están pendientes haría que chocaran; una app real crearía un id en el cliente.
Varias actualizaciones a la vez
Como la función de actualización recibe el estado actual, las actualizaciones optimistas se acumulan. Haz clic rápido y cada clic pendiente se aplica encima del anterior.
Haz clic cuatro veces rápido. El carrito muestra 4 al instante. Cuando terminan las peticiones, el contador queda en 3, porque la tercera petición falló y la consola lo dice. React conserva los valores optimistas hasta que terminan todas las acciones pendientes, y luego muestra el conteo real.
Cuándo usarlo y cuándo no
Las actualizaciones optimistas sirven para acciones que casi siempre tienen éxito y son fáciles de deshacer: me gusta, estrellas, interruptores, renombrar, agregar un mensaje. El usuario no ve ningún indicador de carga en el caso habitual.
Evítalas donde un falso "hecho" engañaría: pagos, borrar una cuenta, cualquier cosa sobre la que el usuario pueda actuar antes de conocer el resultado. Para esos casos, muestra un estado pendiente con isPending de useActionState o useTransition y espera la respuesta real.
Avisa siempre al usuario cuando ocurre una reversión. Un valor que vuelve atrás en silencio parece un error. Los dos ejemplos de arriba guardan un mensaje de error en un estado normal para que sobreviva a la acción.
Con useActionState
useOptimistic y useActionState encajan bien. La acción de useActionState ya es una transición, así que puedes definir el valor optimista al principio, y el estado devuelto es el valor real al que vuelve el optimista.
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);
Mientras se guarda, la página muestra el título nuevo. Cuando se resuelve, state.title contiene el valor guardado. Si saveTitle lanza un error, el error de la acción va al error boundary más cercano; para mostrar el título viejo con un mensaje en su lugar, captura el error dentro de la acción y devuelve el estado anterior más un error.
Errores comunes
Llamar al setter fuera de una acción. El valor optimista no se queda en pantalla, y React avisa en desarrollo. Envuélvelo en startTransition o muévelo a una acción de formulario.
Olvidar actualizar el estado real. Si tiene éxito, la acción debe cambiar el estado que refleja useOptimistic (setLiked, setMessages). Si no, el valor optimista desaparece cuando termina la acción, y una petición exitosa parece una reversión.
Actualizar el estado real después de un await sin una transición. La actualización igual se aplica, pero React puede mostrarla en un momento distinto al final de la acción. Envuélvela en startTransition, como hacen los tres ejemplos.
Usar el valor optimista como fuente de verdad. Envía las peticiones y calcula los totales a partir del estado real. El valor optimista es solo para mostrar y puede descartarse en cualquier momento.
Preguntas frecuentes
¿Qué hace useOptimistic?
Te da una copia de una pieza de estado que puedes cambiar de inmediato mientras se ejecuta una acción async. Cuando la acción termina, la copia vuelve a seguir al estado real, que para entonces contiene el resultado guardado o el valor viejo.
¿Cómo revierte useOptimistic ante un error?
No necesita un camino especial para errores. El valor optimista solo existe mientras la acción está pendiente. Si la petición falla y nunca actualizas el estado real, el hook vuelve a mostrar el estado real, que es el valor de antes del clic.
¿Por qué aparece "An optimistic state update occurred outside a transition or action"?
El setter se llamó desde un manejador de eventos normal. Llámalo dentro de una función pasada a startTransition, dentro de un <form action> o dentro de una acción de useActionState.
¿Cuál es el segundo argumento de useOptimistic?
Una función de actualización opcional, (currentState, optimisticValue) => newState, como un reducer. Con ella el setter recibe solo el cambio (un mensaje nuevo, +1) y React calcula el estado optimista, incluso cuando hay varias actualizaciones pendientes a la vez.
¿useOptimistic es solo para formularios?
No. Funciona en cualquier transición, así que un botón que llama a startTransition(async () => { ... }) puede usarlo igual que una acción de formulario.