Skip to main content

What is useOptimistic

What useOptimistic() is

useOptimistic() is a React hook that lets you temporarily show an updated state in the UI, while an asynchronous action is running (for example, submitting a form, adding a comment, deleting an item, and so on).

In other words: it helps make the interface responsive and "alive", even if the request to the server takes 1-2 seconds.


Signature

javascript
const [optimisticState, addOptimistic] = useOptimistic(baseState, updateFn);
ArgumentDescription
baseStateThe current real state (for example, a list of comments, an array of tasks, etc.)
updateFnA function (state, newValue) => newState describing how to display the "optimistic" state
Returns[optimisticState, addOptimistic] - the current state (accounting for the optimism) and a function to temporarily change it

A real-life example

Suppose you're building a chat where messages are sent to the server. The user clicks "Send", but the response from the API arrives after a second. Without optimism, the UI freezes - the message appears only after the response.

With useOptimistic() you can show the new message immediately, and then React automatically updates the UI once the server responds.


Example: adding a comment

javascript
import { useOptimistic, useActionState } from 'react'; async function postComment(prevState, formData) { const comment = formData.get('comment'); await api.addComment(comment); return { success: true }; } export default function Comments({ comments }) { const [state, formAction] = useActionState(postComment, null); const [optimisticComments, addOptimistic] = useOptimistic( comments, (currentComments, newComment) => [ ...currentComments, { id: Math.random(), text: newComment, pending: true }, ] ); async function handleSubmit(formData) { const comment = formData.get('comment'); addOptimistic(comment); // instantly show the new comment await formAction(formData); // wait for the server } return ( <form action={handleSubmit}> <ul> {optimisticComments.map(c => ( <li key={c.id}> {c.text} {c.pending && '(sending...)'} </li> ))} </ul> <input name="comment" /> <button type="submit">Send</button> </form> ); }

Here:

  • useOptimistic immediately creates a "virtual" list of comments with the new message;
  • When the server responds, React replaces the optimistic state with the real one;
  • The user immediately sees their comment appear.

Other examples

Deleting an item

javascript
const [optimisticTodos, removeOptimistic] = useOptimistic( todos, (current, id) => current.filter(t => t.id !== id) ); async function handleDelete(id) { removeOptimistic(id); // instantly hide it await api.deleteTodo(id); // the server confirms afterward }

Likes

javascript
const [optimisticLikes, toggleOptimistic] = useOptimistic( likes, (current, delta) => current + delta ); async function handleLike() { toggleOptimistic(1); await api.likePost(); }

How this works under the hood

  1. You keep the "real" state (for example, comments or likes).
  2. React builds an "optimistic" state on top of it, which is applied instantly.
  3. When the Action finishes (or the data updates from the server), React discards the optimistic state and syncs with the real one.
  4. If the operation ends in an error, React automatically "rolls back" the UI to the previous state.

The difference from a manual approach (useState)

Without useOptimistic, you would have to write:

javascript
setComments([...comments, newComment]); // manually try { await api.addComment(newComment); } catch { setComments(oldComments); // rollback }

Problems:

  • you need to keep the old state around for the rollback,
  • it's hard to sync with other updates,
  • it can conflict with concurrent rendering.

With useOptimistic:

  • React itself tracks the "virtual" changes,
  • it automatically syncs and rolls back the state,
  • it works safely under concurrent rendering.

When to use useOptimistic

It fits for:

  • Interfaces with response delays (forms, comments, likes, carts);
  • Lists and tables where items are added/removed;
  • UX optimization - the user sees the action "instantly".

It should not be used:

  • For data where an error must block the action (for example, payments);
  • For state that depends on complex server-side transactions;
  • When the "optimistic" version could confuse the user.

Comparison with other React 19 hooks

HookPurpose
useActionState()Manages the state of an asynchronous action (result, error, pending)
useFormStatus()Tracks the submission status of a specific form
useOptimistic()Temporarily updates the UI for instant feedback
useTransition()Makes updates "low priority", but not optimistic

Combined example: useOptimistic + useActionState

javascript
async function addTask(prev, formData) { const task = formData.get('task'); await api.addTask(task); return { success: true }; } export default function TaskList({ tasks }) { const [state, formAction] = useActionState(addTask, null); const [optimisticTasks, addOptimistic] = useOptimistic( tasks, (list, newTask) => [...list, { id: Math.random(), text: newTask, pending: true }] ); async function handleSubmit(formData) { const task = formData.get('task'); addOptimistic(task); await formAction(formData); } return ( <form action={handleSubmit}> <ul> {optimisticTasks.map(t => ( <li key={t.id}>{t.text} {t.pending && '(saving...)'}</li> ))} </ul> <input name="task" /> <button type="submit">Add</button> </form> ); }

The user sees the new task instantly, and React finalizes it once the server confirms.


Summary

useOptimistic() is a React hook for building an instant UI response to asynchronous changes, while the server processes the action.

It is perfect for UX where the sense of "speed" matters, while React itself manages the rollback and data reconciliation.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.