MENU
useOptimistic
useOptimistic() is a Hook, introduced in React 19, for showing an optimistic value immediately while an asynchronous action is still in flight, then automatically reconciling back to the real value once that action finishes. It takes the current real state and an update function, and returns [optimisticState, addOptimistic]. Rendering should use optimisticState in place of the real state — it equals the real state whenever nothing is pending, and reflects the optimistic update while something is.
The update function receives (currentState, optimisticValue) — where optimisticValue is whatever gets passed to addOptimistic() — and returns the new optimistic state, the same way a useReducer() reducer combines state with an action. It can be omitted entirely, in which case the value passed to addOptimistic() is used as the new optimistic state directly. If the real state changes while an action is still pending, React re-runs the update function against that new state, so the optimistic value never reflects stale data.
addOptimistic() must be called from inside a transition — typically from inside an action function passed to a <form>'s action prop, or a callback passed to startTransition(). Calling it outside of one, say from a plain onClick handler, logs a warning, and the optimistic value is discarded immediately since there's no pending transition to keep it alive. There's no extra render needed to clear the optimistic state afterwards; it converges with the real state in a single render once the action completes.
useOptimistic() is frequently paired with useActionState(), covered previously — see the combined example on the following page.
RESETRUNFULL
<!DOCTYPE html><html><head>
<script type="importmap">
{"imports": {"react": "https://esm.sh/react@19", "react-dom/client": "https://esm.sh/react-dom@19/client"}}
</script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head><body>
<div id="root"></div>
<script type="text/babel" data-type="module" data-presets="react">
import { useState, useOptimistic } from 'react';
import { createRoot } from 'react-dom/client';
async function saveMessageToServer(text) {
await new Promise((resolve) => setTimeout(resolve, 1000)); // simulate network latency
return text;
}
function App() {
const [messages, setMessages] = useState([{ text: 'Hi there!', sending: false }]);
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(currentMessages, newText) => [...currentMessages, { text: newText, sending: true }]
);
async function formAction(formData) {
const text = formData.get('message');
addOptimisticMessage(text);
const saved = await saveMessageToServer(text);
setMessages(prev => [...prev, { text: saved, sending: false }]);
}
return (
<div>
<ul>
{optimisticMessages.map((message, i) => (
<li key={i}>{message.text}{message.sending ? ' (sending...)' : ''}</li>
))}
</ul>
<form action={formAction}>
<input type="text" name="message" />
<button type="submit">Send</button>
</form>
</div>
);
}
createRoot(document.getElementById('root')).render(<App />);
</script>
</body></html>