Have you ever clicked? Like icon on a social media app and notice the count jump immediately? The icon color changes simultaneously, before the server action ends.
Now imagine you hit a similar button, but it takes its sweet time to respond to you by making server calls, performing DB updates, and updating the button’s state.
Which experience would you like the most? You are more likely to choose the first scenario. We all love “instant feedback”. The magic of instant feedback works through a pattern called Optimistic UI Pattern.
In this article, we will uncover:
What does Optimistic UI really mean?
Why does this massively improve the user experience?
19’s new UseOptimistic() hook is react?
How to implement a real-world scenario using an optimistic paradigm
A bunch of use cases where you’ll be able to use this pattern.
In the end, you’ll be actively thinking about using this design pattern to improve the UX of your project.
This article is also available as a video tutorial 15 days of reactive design Action. Please check it out.
https://www.youtube.com/watch?v=x03yx-ynxas
Table of Contents
What is Optimistic UI?
Optimistic UI (also known as optimistic updates) is a pattern that helps you update the UI immediately, assuming that the server operation will succeed, and if it later fails, you roll the UI back to the correct state.
Instead of waiting for a round trip of the client request, write to the database, server response, and then render the UI, the UI just updates immediately. This gives rise to a dramatic effect called perceived speed. The user of the application perceives the UI update immediately – but the actual operation may occur in the background.
Without an optimistic update:
If you’re not using the optimistic pattern, this is just a traditional client-server mechanism, where:
On the client side, a user interacts with the UI element.
one async call (request) has been created to the server.
The server processes the request and can update the DB.
On a successful case, the server sends a response back to the client.
The client updates the corresponding UI.
In case of an error, the server sends back an error response to the client.
The client notifies the user of the error.

In this case, the user has to wait for the success/failure of the request to understand any changes after their interaction. This wait is neither uniform nor optimal. This may vary based on network speed, network latency, and application deployment strategy.
With an optimistic update:
When you’re using an optimistic prospect, here’s how things work:
On the client side, a user interacts with the UI element.
The UI updates instantly, and the user feels the feedback immediately.
In parallel, in the background, the client initiates the server call.
The server processes the request and can update the DB.
On a successful case, the server does nothing else, because the UI has already been updated, assuming the call will succeed.
In case of an error, the server sends back an error response to the client.
The client returns the impatient, optimistic UI update it made.

In this case, the user does not wait for the server round-trip to complete before updating the UI. This is very fast, assuming that, in most cases, the parallel server call will succeed.
With this comparison, we can now understand why up-to-date information is important in modern UI.
And so are optimistic updates for real-time sensing features like chat messages, likes, comments, cart updates, poll votes, collaborative editing, and more. Even AI-driven apps that take time to exploit promising niches like “Think…”, “Send…”
How does it work under Bandit?
Under the hood, there are actually two states:
Original state: It is the original source of reality. This data should be synchronized with the server.
Optimistic state: It is temporary and shown to the user immediately.
When the server request succeeds, do nothing. Your optimism is now correct. If the server request fails, perform a rollback, and return the UI to its original state.
React 19 introduced a built-in hook to help with this pattern called useOptimistic() . In the next section, we’ll dive deep into it with code and working internals.
useOptimistic() Hook in reaction 19
useOptimistic() There is a React hook introduced in React 19 to help with Hope updates. The syntax and usage of hook is as follows:
const (optimisticState, addOptimistic) = useOptimistic(state, updateFn);
When an async action is in progress, useOptimistic() A hook allows you to display different states.
It accepts:
Current state: your actual source of truth (usestate, redux, serverstate, and so on)
Update fn: A pure function that tells how to compute the expected value
It returns:
Optimistic State: Temporary UI state
Optimistic update (input): function you call to apply immediate updates
Take a look at the image below. It clearly shows the relationship between the current state and the desired state:

Here’s what’s going on:
We transfer the current state and an updated function to the Lord
useOptimistichookThe updater function takes the current state and user input to compute and return the next optimistic state.
Input is provided using the updater function
addOptimistic(input)The ceremonyFinally, a partially optimistic state value is used.
Let’s now create something interesting using this hook to better understand its internals.
How to Make Hope Like a Button
We will hopefully build in similar button functionality. The flow will be as follows:
The user clicks on the Like button.
As we update the button state quickly and hopefully.
In parallel, we send a server call to persist the value in the DB (we will simulate it)
Then we handle any error scenarios.
First, simulate a network call to the server using JavaScript’s Promise object and setTimeout() Web API:
async function sendLikeToServer(postId) {
await new Promise((r) => setTimeout(r, 700));
if (Math.random() < 0.2) throw new Error("Network failed");
console.log(`Sent a like for the post id ${postId}`);
return { success: true };
}
sendLikeToServer The function takes a post ID as a parameter and simulates a fake network call using a promise and a delay of 700 ms. It pretends to make a request to the server to maintain the value of a post’s likes.
To make it a little more realistic, we’ve created a random error. The function will randomly throw an error so we can also understand the rollback scenario.
Next, we’ll create the actual state for the actual source, such as computation:
const (likes, setLikes) = useState(initialLikes);
Then, create the value of the optimistic state with the expectation useOptimistic() hook:
const (optimisticLikes, addOptimisticLike) = useOptimistic(
likes, (currentLikes, delta) => currentLikes + delta);
Let’s understand this declaration better:
We have passed the original state value (like) and the updater function
useOptimistic()hookTake a look at the updater function,
(currentLikes, delta) => currentLikes + delta. This is an arrow function that combines the current value and delta like. It returns the sum of the current value and the delta. The return value logic is your own business logic. To increase such a count, it makes sense to increase the value of the current by the delta value (1).Now, the question is how do we get this delta value? Who passes it? Return values of the same location
useOptimistic()come to workaddOptimisticLikeThere is a function through which we can pass this delta value. How? Let’s take a look.
When someone clicks on the like button, we need to handle the click event and increment the value of the like count. So here’s one handleLike() A function that does:
const handleLike = async () => {
addOptimisticLike(1);
try {
await sendLikeToServer(postId);
setLikes((prev) => prev + 1);
} catch (err) {
console.error("Like failed:", err);
setLikes((s) => s);
}
};
There’s a lot going on here:
We say
addOptimisticLike()Work with a delta value of 1. This call will ensure that the updater function(currentLikes, delta) => currentLikes + deltaOfuseOptimistic()will be called. Expectation on the return price will be set to the expectation condition, that is,optimisticLikes.We use this optimistic state value in JSX. So we can immediately see the count growing like this.
Then we call the fake server, and also update the original state, provided the server call is successful.
In case of an error, control goes to the catch block, where we return the value of choice to the previous one. This will also synchronize the optimistic state value with the rollback.
Here is the complete code LikeButton Ingredients:
import { startTransition, useOptimistic, useState } from "react";
async function sendLikeToServer(postId) {
await new Promise((r) => setTimeout(r, 700));
if (Math.random() < 0.2) throw new Error("Network failed");
console.log(`Sent a like for the post id ${postId}`);
return { success: true };
}
export default function LikeButton({ postId, initialLikes = 0 }) {
const (likes, setLikes) = useState(initialLikes);
const (optimisticLikes, addOptimisticLike) = useOptimistic(
likes,
(currentLikes, delta) => currentLikes + delta
);
const handleLike = async () => {
addOptimisticLike(1);
try {
await sendLikeToServer(postId);
setLikes((prev) => prev + 1);
} catch (err) {
console.error("Like failed:", err);
setLikes((s) => s);
}
};
return (
<div className="flex">
<button onClick={handleLike}>❤️ {optimisticLikes}button>
<button onClick={() => startTransition(async () => handleLike())}>
❤️ {optimisticLikes}
button>
div>
);
}
Have you seen that we wrapped? handleLike() Call with startTransition?
Without it, React gives us a warning:
“An optimistic state update has occurred outside of the transfer or process.”
This is because there are optimistic updates A low-priority visual updatenot critical.
By using startTransition() Ensures that:
Does not inhibit the reaction
We don’t get a warning
We get a smooth, optimistic experience
Transitions are part of React’s concurrency model that helps us improve the performance of React applications. If you are interested in learning different performance improvement techniques, Here’s a two-part guide for you.
Disadvantages and anti-patterns
As with any design pattern, we need to be aware of potential pitfalls, misuses, and anti-patterns. Here are some things you should be aware of:
Do not assume that the server call will succeed. There will be network failures, and you need to have a way to roll back. Expected rollback is the heart of UI. Omitting the rollback logic will produce negative results.
Don’t try to hide bad UX behind optimistic updates. Optimistic UI is not a fix or replacement for poor designs.
Don’t do anything expensive in optimistic updates. Keep the optimistic updater function lean, pure and fast.
15 days of reactive design
I have some great news for you: after me 40 Days of JavaScript Initiative, I have now launched a brand new initiative called 15 days of reactive design.
If you enjoyed learning from this article, I’m sure you’ll love this series, which includes 15+ of the most important reactive design patterns. Check it out and join for free:
Before we end…
All this! I hope you find this article insightful. You can find all the source code used in this tutorial Tapascript Github.
See you soon with my next article. Until then, please take care and keep learning.
