Skip to main content

Command Palette

Search for a command to run...

Fake it Until Make it: Optimistic UI in React Native

Published
6 min read
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:

  1. User taps a button

  2. App waits for the network to respond

  3. Server returns a result

  4. UI updates

⚡ Optimistic UI flips this entirely:

  1. User taps a button

  2. UI updates immediately

  3. Network request happens in the background

  4. 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

😜
Everyone has seen this on Instagram — now it makes sense, right?

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:

  1. Update the UI locally

  2. Sync changes in the background

  3. Retry until success

  4. 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:

  1. 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.

  1. 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.

  2. 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! 🚀