HomeAbout Me

React Suspense: Optimistic UI

By Daniel Nguyen
Published in React JS
June 23, 2025
2 min read
React Suspense: Optimistic UI

⚡ Building Fast and Fluid Experiences with Optimistic UI in React

When users interact with your UI—clicking buttons, submitting forms, checking off items—they expect it to respond instantly. But when those actions involve network requests, even a small delay can make the UI feel sluggish.

That’s where Optimistic UI comes in.

Instead of waiting for the server to respond before updating the UI, we optimistically assume it will succeed and update the interface immediately. In this post, you’ll learn how to implement Optimistic UI with React’s powerful tools like useOptimistic and useFormStatus, plus handle multi-step form transitions—all without hurting user experience.


✨ What is Optimistic UI?

Optimistic UI is based on a simple assumption:

“Most of the time, user actions will succeed.”

So rather than waiting for confirmation from the server, we immediately update the UI with what we expect to happen. If something goes wrong, we can always roll it back.

Example: If a user checks off a todo, we mark it complete instantly and send a request in the background.

✅ Fast feedback → ⚡ Better UX

You can learn more about this concept at the end of this talk by Kent C. Dodds.


🧠 Enter useOptimistic: Optimism Inside Transitions

React Suspense and useTransition are great for deferring UI changes until data is ready. But what if we want to optimistically change the UI even while it’s still suspended?

That’s what useOptimistic is for. It works like useState, but lets you override state during a transition (such as when submitting a form). This makes it ideal for implementing optimistic UI.

✅ Example: Optimistically Toggle a Todo Item

function Todo({ todo }: { todo: TodoItem }) {
const [isComplete, setIsComplete] = useOptimistic(todo.isComplete)
return (
<form
action={async () => {
setIsComplete(!isComplete) // Update UI optimistically
await updateTodo(todo.id, !isComplete) // Perform server update
}}
>
<label>
<input
type="checkbox"
checked={isComplete}
className="todos-checkbox"
/>
{todo.text}
</label>
</form>
)
}

Notice how isComplete updates instantly, even before the server responds. Once the action completes (success or error), React will re-render using the actual prop value again (todo.isComplete).


🧪 useFormStatus: Track Submission State

Sometimes you just want to let users know the form is submitting, maybe disable the button or change its label. That’s where useFormStatus shines.

You can think of the <form> as a context provider, and useFormStatus as the consumer.

function SubmitButton() {
const formStatus = useFormStatus()
return (
<button type="submit" disabled={formStatus.pending}>
{formStatus.pending ? 'Creating...' : 'Create'}
</button>
)
}

With this, your button knows when the form is submitting and adjusts accordingly.

📚 Learn more about useFormStatus


🚀 Optimistic Form Submission in Action

Let’s say you’re building a page to create new starships.

The issue: When the user submits the form, there’s a noticeable delay before the new ship appears. This feels slow—even if your API is fast!

Instead, we’ll:

  1. Use createOptimisticShip(formData) to simulate the new ship.
  2. Show that ship immediately while we wait for the real data.
  3. Update the real data once it loads.

Step 1: Create the Optimistic Ship

Inside your form action:

action={async (formData) => {
const optimisticShip = createOptimisticShip(formData)
setOptimisticShip(optimisticShip)
const realShip = await createShip(formData)
setSelectedShip(realShip.name)
setOptimisticShip(null)
}}

Here, createOptimisticShip(formData) creates a ship object instantly (e.g., with fetchedAt: '...'). We display that in the UI while waiting for createShip() to finish.


🎯 Hooking Everything Up

You’ll need to lift state up to the parent <App> component so that CreateForm and ShipDetails can both access and modify the optimistic ship:

function App() {
const [selectedShip, setSelectedShip] = useState(null)
const [optimisticShip, setOptimisticShip] = useState(null)
return (
<>
<CreateForm setOptimisticShip={setOptimisticShip} setSelectedShip={setSelectedShip} />
<ShipDetails ship={optimisticShip ?? selectedShip} />
</>
)
}

This lets you render the optimistic ship first, and the actual one later when it arrives.


🧩 Bonus: Multi-Step Actions with useOptimistic

What if your form action does multiple steps?

<form
action={async (formData) => {
setMessage('Creating ship...')
const ship = await createShip(formData)
setMessage('Saving to database...')
await saveShipToDb(ship)
setMessage('Almost done...')
await notifyFleet(ship)
}}
>
<SubmitButton />
</form>

The problem? You can’t update local state like setMessage inside a transition… unless you use useOptimistic.

✅ Add an Optimistic Message

function CreateForm() {
const [message, setMessage] = useOptimistic('Create')
return (
<form
action={async (formData) => {
setMessage('Creating ship...')
const ship = await createShip(formData)
setMessage('Saving to DB...')
await saveShipToDb(ship)
setMessage('Almost done...')
await notifyFleet(ship)
}}
>
<SubmitButton message={message} />
</form>
)
}
function SubmitButton({ message }: { message: string }) {
const formStatus = useFormStatus()
return <button type="submit" disabled={formStatus.pending}>{message}</button>
}

Now, your submit button gives step-by-step feedback to the user—exactly what’s happening and when.


💡 Final Thoughts

Optimistic UI is a powerful tool to enhance user experience and build responsive apps that feel instant.

TL;DR:

  • Use useOptimistic to override UI state during transitions.
  • Use useFormStatus to monitor form state and show submission feedback.
  • Combine these to optimistically render content and provide multi-step feedback.
  • Use fallback values like "..." for fields not yet fetched.

📚 Further Resources



Tags

#React

Share

Previous Article
React Suspense: Dynamic Promises

Table Of Contents

1
✨ What is Optimistic UI?
2
🧠 Enter useOptimistic: Optimism Inside Transitions
3
🧪 useFormStatus: Track Submission State
4
🚀 Optimistic Form Submission in Action
5
🎯 Hooking Everything Up
6
🧩 Bonus: Multi-Step Actions with useOptimistic
7
💡 Final Thoughts
8
📚 Further Resources

Related Posts

React Suspense: Optimizations
June 26, 2025
2 min
© 2025, All Rights Reserved.
Powered By

Quick Links

About Me

Legal Stuff

Social Media