Fake it Until Make it: Optimistic UI in React Native

Have you ever tapped a button in an app and 💨
nothing happened?
No loading indicator, no error message, no hint that your action was even received.
Just you… staring at the button you tapped.
What do you do now?
Tap again?
Wait?
Or — God forbid — just leave?
Modern apps are expected to feel instant.
But the reality is: networks are slow, servers are unpredictable, and offline conditions are very real.
This is where Optimistic UI comes in.
Optimistic UI flips the traditional workflow upside down:
Instead of waiting for the server to confirm an action, the app immediately updates the UI as if everything succeeded, creating a fast, fluid, “this app does exactly what I want — instantly” feeling.
Or in short:
✨✨✨ Fake it until you make it. ✨✨✨
In this article, we’ll break down what Optimistic UI is, why it matters, and how you can implement it cleanly and safely in React Native apps — especially when building offline-first experiences.
What Is Optimistic UI?
At its core, Optimistic UI is a design strategy where your app assumes success and updates the interface before the server confirms the result.
🐢 The traditional workflow looks like this:
User taps a button
App waits for the network to respond
Server returns a result
UI updates
⚡ Optimistic UI flips this entirely:
User taps a button
UI updates immediately
Network request happens in the background
If it fails → you roll back or show a correction
The real strategy is simple:
Treat success as the default outcome — unless proven otherwise.
But more importantly, it aligns how users think with how the app behaves.
When a user performs an action, they already mentally assume it worked—your UI simply matches that expectation.
A simple real-life example
Let’s say a user “likes” a post.
Without Optimistic UI:
tap → spinner/loader → “like” finally turns red
With Optimistic UI:
tap → instantly red ❤️ → request happens in the background → if failure, revert
Which one feels smooth—and doesn’t bother you with unnecessary waiting?
Exactly👌.
📍 Why Does Optimistic UI Matter?
Optimistic UI isn’t just a “nice to have.”
It’s the difference between an app that feels slow and one that feels alive.
Modern users expect apps to respond instantly — not when the server is ready, but when they are.
Here’s why it matters:
Perceived performance goes up — even if real performance doesn’t
The network is slow, and sometimes the servers are even slower.
But with optimistic UI:
a like appears instantly
a toggle flips immediately
the screen reacts the moment you tap
…even if the server takes 1000ms to respond.
/It’s not magic — just a little psychology.
It improves UX for slow or unstable networks
Some users commute underground.
Mobile data drops.
People switch between Wi-Fi and 4G constantly.
In these conditions, offline-first apps must use optimistic UI.
Otherwise, every action would freeze the screen.
It’s essential for offline-first architecture
Offline-first workflows usually look like this:
Update the UI locally
Sync changes in the background
Retry until success
Handle conflicts only if needed
Without optimistic updates, the whole offline-first strategy breaks.
You simply can’t pause the UI while waiting for a network that may not even exist.
⭐ How to Implement Optimistic UI in React Native
Implementing Optimistic UI in React Native is not complicated
But it requires a clear structure so you don’t end up with confusing states or inconsistent UI.
This pattern has 3 levels:
Update the UI immediately
When the user performs an action (like, delete, add, or toggle), you instantly update your local UI state as if the operation succeeded.
setLike(true);No waiting, no loading or spinning.
Send the network request in the background
Right after updating the UI, trigger the actual API request.
api.likePost(postId);Your UI does not depend on this result yet — the user already sees the updated state.
Roll back if the request fails
If the server rejects the action, you revert the UI and optionally notify the user.
try { await api.likePost(postId); } catch (error){ setLike(false) // here is the revert part Toast.show("Something went wrong") //optional }Rollback is the “safety net” that keeps optimistic UI safe.
📱A Complete Example (React Native + useState)
const Post = ({ postId }) => {
const [liked, setLiked] = useState(false);
const handleLike = async () => {
// OPTIMISTIC UPDATE ⚠️
setLiked(true);
try {
// BACKGROUND REQUEST
await api.likePost(postId);
} catch (error) {
// Rollback ⚠️
setLiked(false);
}
};
return (
<TouchableOpacity onPress={handleLike}>
<Text>{liked ? "❤️ Liked" : "🤍 Like"}</Text>
</TouchableOpacity>
);
};
For a More Scalable Pattern, we can use React Query / TanStack Query:
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: api.likePost,
// Optimistic update ⚠️
onMutate: async (postId) => {
await queryClient.cancelQueries(["post", postId]);
const previous = queryClient.getQueryData(["post", postId]);
queryClient.setQueryData(["post", postId], (old) => ({
...old,
liked: true,
}));
return { previous };
},
// Rollback on error
onError: (err, postId, context) => {
queryClient.setQueryData(["post", postId], context.previous);
},
// Sync with server
onSettled: (postId) => {
queryClient.invalidateQueries(["post", postId]);
}
});
Using this pattern keeps the server state synchronized with the UI.
Rollback becomes safer, clearer, and — the reason I wrote this article — it works beautifully for offline-first architectures.
🚫 When NOT to Use Optimistic UI
Optimistic UI is amazing… but not for every scenario.
❌ Payment operations
❌ Destructive actions (permanent delete)
❌ Multi-user real-time conflict-prone data
❌ High-risk actions (money, financial transactions)
❌ Security-critical flows (password change, account deletion, permission updates)
❌ Complex or multistep backend workflows
❌ Large-scale or bulk data changes
Offline-First + Conflict Handling (Short Summary)
Optimistic UI becomes even more powerful when combined with an offline-first architecture.
When a device goes offline, your app must update the UI instantly, store the changes locally, and sync them later — without confusing the user (it is a good example of using TanStack ** we talked about it before).
But it has one issue: ⭐ conflicts ⭐
When the server and client both update the same data, you need a strategy:
Last-Write-Wins (LWW):
The most recent change wins. Simple, but sometimes it is the first idea, but not works well.
Merge Logic:
Combine changes from both sides. Useful for lists, counters, and preferences.
Asking User (not my style tho):
In some cases, showing a “Your data changed, which one do you want?” dialog is necessary.
So, Optimistic UI is not just a performance trick — it’s a mindset.
It makes your app feel fast, modern, and resilient, even under unstable network conditions.
And for offline-first products, it’s practically a requirement.
If you enjoyed this breakdown, let me know — I’m planning a follow-up on building a complete offline-first React Native architecture with caching, syncing, and conflict handling.
Thanks for reading — and happy building! 🚀