Join our WhatsApp Channel
Cart / ₹0.00

No products in the cart.

Visit Support Portal
No Result
View All Result
Monday, October 12, 2026
  • Home
  • Tutorials
    • Machine Learning
    • Python
    • Web Development
    • Javascript
    • Mathematics
    • Deep Learning
    • Artificial Intelligence
  • Store
  • Our ServicesBook Now
    • Managed Hosting Services
    • Web Development Services
    • Digital Marketing Services
  • Our Softwares
    • JaggoRe AIFree
    • Email ExtractorPaid
    • TimeWell AppFree
Get a Free Quote
  • Login
  • Register
Neuraldemy
Monday, October 12, 2026
Cart / ₹0.00

No products in the cart.

No Result
View All Result
Neuraldemy
Get A Free Quote
Home Web Development

Preventing Out-of-Order Rollbacks in Optimistic UI: A Step-by-Step React Tutorial

Neuraldemy by Neuraldemy
October 12, 2026
in Web Development, Javascript
Reading Time: 18 mins read
A A

In this tutorial we will learn about preventing out-of-order rollbacks in optimistic UI. Imagine you are using a web app and you click an “Upvote” button. In an ordinary app, the button does nothing for half a second while a request travels to the server and back. Then the number goes from 10 to 11. In an optimistic UI, the app does not make you wait. The moment you click, the number immediately jumps to 11. Behind the scenes, the browser sends the request to the database. If everything goes well, you never notice any delay.

This works great when someone clicks once and waits. But real people do not use apps that way. Users double-click by mistake. They click “Like”, change their mind a second later, click “Unlike”, and then click “Like” again.

When someone clicks quickly, your app sends multiple network requests at almost the same time. And the internet does not guarantee that the first message sent will be the first message to arrive.

Table of Contents

  • Why People Use Loading Spinners (And What Is Wrong With Them)
    • So why stop using loading spinners?
  • The Wrong Way: The Naive Rollback Trap
  • The Right Way To Rollback UI
  • A Simple Coding Example:
    • How this hook works:
  • Using the Hook in a Real Component
  • Frequently Asked Questions
    • Can I use Date.now() instead of a counter?
    • What if every single request fails?
    • Does this replace AbortController?

Why People Use Loading Spinners (And What Is Wrong With Them)

You might ask: “Why not just disable the button and show a loading spinner?”

That is what most websites do:

  1. Set isLoading = true.
  2. Disable the button.
  3. Show a spinning icon.
  4. When the server answers, enable the button again.

Does this stop race conditions? Yes, completely. If the user cannot click, they cannot create out-of-order requests.

So why stop using loading spinners?

Because loading spinners make your app feel slow and sticky. Think about apps like X, Slack, or Notion. If social feeds froze the heart icon with a loading spinner every time you tapped “Like” on mobile, the entire app would feel sluggish. You could not quickly scroll and like posts in a row. Modern web apps use optimistic UI because they want the interface to feel instant. But the moment you remove the loading spinner and let people click freely, you open the door to a nasty bug: the out-of-order rollback. But this does not mean you should avoid spinners in every app. First make sure the kind of experience you want to offer, or the nature of the platform.

The Wrong Way: The Naive Rollback Trap

When developers first try to build an optimistic update without a spinner, they usually write code like this:

const handleToggle = async () => {
  const previousState = isUpvoted;
  setIsUpvoted(!isUpvoted);

  try {
    await sendVoteToServer(!isUpvoted);
  } catch (error) {
    setIsUpvoted(previousState);
  }
};

This looks clean:

  • Save previousState.
  • Immediately flip the UI.
  • Send the update to the server.
  • If the server throws an error, restore previousState.

Now look at what happens in production when a user clicks twice quickly:

  1. Click 1: The vote is false. You save previousState = false, update the screen to true, and fire Request 1.
  2. Click 2: One second later, the user clicks again. You save previousState = true, update the screen to false, and fire Request 2.
  3. The Network Issue: Request 2 takes a fast network route and succeeds in 100 milliseconds. The server confirms the vote is now false.
  4. The Failure: Request 1 gets delayed, fails after 2 seconds, and hits the catch block.

Request 1 runs setIsUpvoted(previousState). Because previousState was captured during Click 1, it resets the screen to stale data. Request 1’s late failure blows away the user’s newest choice from Request 2. The button flickers, jumps backward, and shows the wrong state.

The Right Way To Rollback UI

To fix this, we need a simple rule: Newer actions always beat older actions. The goal is to keep track of states, and if something goes back restore the last confirmed state that server had notified.

Think of it like taking a numbered paper ticket counter:

  • Every time you click, you pull a ticket with the next number (Ticket 1, Ticket 2, Ticket 3).
  • When a request returns from the server, we check its ticket number.
  • If Ticket 1 fails, but you are already holding Ticket 2, you do nothing. You ignore Ticket 1’s failure because Ticket 2 is now in charge.
  • You only reset the screen if the ticket that failed is the latest ticket you have.

This lets users click as fast as they want without ever freezing the button, and it guarantees that an old failed request cannot undo a newer action.

A Simple Coding Example:

Let us build a small, reusable React hook called useOptimisticAction:

import { useState, useRef, useCallback } from 'react';

interface Options<T> {
  initialValue: T;
  mutateFn: (value: T) => Promise<T>;
  onError?: (error: Error) => void;
}

export function useOptimisticAction<T>({
  initialValue,
  mutateFn,
  onError,
}: Options<T>) {
  const [value, setValue] = useState<T>(initialValue);
  const sequenceRef = useRef(0);
  const settledRef = useRef(0);
  const baselineRef = useRef<T>(initialValue);

  const performAction = useCallback(
    async (nextValue: T) => {
      sequenceRef.current += 1;
      const currentTicket = sequenceRef.current;

      setValue(nextValue);

      try {
        const serverResult = await mutateFn(nextValue);

        if (currentTicket >= settledRef.current) {
          settledRef.current = currentTicket;
          baselineRef.current = serverResult;
          setValue(serverResult);
        }
      } catch (err) {
        const error = err instanceof Error ? err : new Error('Network error');

        if (currentTicket >= sequenceRef.current) {
          setValue(baselineRef.current);
          onError?.(error);
        }
      }
    },
    [mutateFn, onError]
  );

  return { value, performAction };
}

How this hook works:

  1. sequenceRef: Every time the user clicks, we add 1 to sequenceRef.current. That gives this call a unique currentTicket number.
  2. baselineRef: This holds the last value the server confirmed. We only roll back to this value if something breaks.
  3. The Success Check (currentTicket >= settledRef.current): When a request succeeds, we make sure it is not older than something already settled. If it is fresh, we update baselineRef.
  4. The Failure Check (currentTicket >= sequenceRef.current): When an error hits, we check if currentTicket is still the newest ticket. If the user clicked again while this request was running, sequenceRef.current is higher. The hook safely ignores the old error and leaves the newer state alone.

Using the Hook in a Real Component

Here is how you use this hook in a clean button component:

import React from 'react';
import { useOptimisticAction } from './useOptimisticAction';

async function syncVoteWithServer(newVoteState: boolean): Promise<boolean> {
  const response = await fetch('/api/upvote', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ upvoted: newVoteState }),
  });

  if (!response.ok) {
    throw new Error('Server rejected vote');
  }

  const data = await response.json();
  return data.upvoted;
}

export function UpvoteButton({ defaultUpvoted = false }: { defaultUpvoted?: boolean }) {
  const { value: isUpvoted, performAction } = useOptimisticAction({
    initialValue: defaultUpvoted,
    mutateFn: syncVoteWithServer,
    onError: (err) => {
      console.warn('Could not save vote:', err.message);
    },
  });

  return (
    <button
      type="button"
      onClick={() => performAction(!isUpvoted)}
      className={isUpvoted ? 'vote-btn active' : 'vote-btn'}
    >
      {isUpvoted ? 'Upvoted' : 'Upvote'}
    </button>
  );
}

Notice what we achieved:

  • No button freezing or disabling.
  • No loading spinner blocking the user.
  • If the user clicks multiple times rapidly, only the newest intent survives.
  • If all network requests fail, the button cleanly resets to the last confirmed server state.

Optimistic UI makes modern websites feel snappy and responsive. While a loading spinner is the easiest way to avoid race conditions, it destroys that instant feel.

By adding a simple monotonic sequence counter, you get the best of both worlds:

  • Instant feedback on every click.
  • Full protection against out-of-order network responses.
  • Clean error recovery when network failures occur.

Frequently Asked Questions

Can I use Date.now() instead of a counter?

No. Computer and phone clocks can shift when reconnecting to networks. An integer counter (+ 1) is guaranteed to always increase in order.

What if every single request fails?

Our hook handles this automatically. If all requests fail, the last request’s currentTicket matches sequenceRef.current, so it safely restores baselineRef.current (the last confirmed server state).

Does this replace AbortController?

No, you can combine both. AbortController stops client HTTP processing, but it cannot guarantee the server aborted database operations. Sequence tracking protects the UI either way.

If you need a simpler approach to this React also provides a hook. Check out this React Tutorial.

Support

Buy author a coffee

Support
SummarizeSendShareTweetScan
Previous Post

100% Working Hostinger Discount Coupons & Promo Codes (2026)

Next Post

Handling Bfcache with Active WebSocket Connections in React: A Complete Tutorial

Neuraldemy

Neuraldemy

This is Neuraldemy support. Subscribe to our YouTube channel for more.

Related Posts

Preventing Infinite Echo Loops in React Broadcast Channel

Handling Bfcache with Active WebSocket Connections in React: A Complete Tutorial

100% Working Hostinger Discount Coupons & Promo Codes

100% Working Hostinger Discount Coupons & Promo Codes (2026)

Honest Review: Data Structures And Algorithms Made Easy By Narasimha Karumanchi

JavaScript Concepts Revision For Interview

Git Tutorial: A Beginners Guide To Git And GitHub

Next Post

Handling Bfcache with Active WebSocket Connections in React: A Complete Tutorial

Preventing Infinite Echo Loops in React Broadcast Channel

Support

Buy author a coffee

Support

Support Neuraldemy

Newsletter

Top rated products

  • Probability and Statistics for Machine Learning and Data Science Probability and Statistics for Machine Learning and Data Science
    Rated 5.00 out of 5
    ₹599.00 Original price was: ₹599.00.₹349.00Current price is: ₹349.00.
  • SVM Notes: Optimization & Implementation SVM Notes: Optimization & Implementation
    Rated 5.00 out of 5
    ₹399.00 Original price was: ₹399.00.₹249.00Current price is: ₹249.00.
  • spidy mail hunter software Spidy Mail Hunter: Powerful And Intelligent Website & PDF Email Extractor Software (Windows App)
    Rated 4.78 out of 5
    ₹9,999.00 Original price was: ₹9,999.00.₹599.00Current price is: ₹599.00.
  • Linear Algebra For Machine Learning And Data Science Linear Algebra For Machine Learning And Data Science
    Rated 4.71 out of 5
    ₹699.00 Original price was: ₹699.00.₹449.00Current price is: ₹449.00.
  • Javascript DOM Notes Javascript Document Object Model Notes And Codes ₹499.00

Recent Posts

Preventing Infinite Echo Loops in React Broadcast Channel

Handling Bfcache with Active WebSocket Connections in React: A Complete Tutorial

Preventing Out-of-Order Rollbacks in Optimistic UI: A Step-by-Step React Tutorial

October 2026
M T W T F S S
 1234
567891011
12131415161718
19202122232425
262728293031  
« Aug    

Neuraldemy

Neuraldemy

Neuraldemy

Software Development

Neuraldemy helps you learn ML, AI, Web Dev and data science from scratch. Addtionally, we also provide web development and hosting services for businesses, and individuals

  • Get Started
  • Contact
  • Book Services
  • Privacy Policy
  • Terms Of Use
  • Support Portal
  • Managed Hosting Terms
  • Web Development Terms
  • Refund Policy
Neuraldemy

© 2026 - A learning platform by Odist Magazine

Welcome Back!

Login to your account below

Forgotten Password? Sign Up

Create New Account!

Fill the forms below to register

*By registering into our website, you agree to the Terms & Conditions and Privacy Policy.
All fields are required. Log In

Retrieve your password

Please enter your username or email address to reset your password.

Log In
No Result
View All Result
Join Our WhatsApp Channel
  • Home
  • Tutorials
    • Machine Learning
    • Python
    • Web Development
    • Javascript
    • Mathematics
    • Deep Learning
    • Artificial Intelligence
  • Store
  • Our Services
    • Managed Hosting Services
    • Web Development Services
    • Digital Marketing Services
  • Our Softwares
    • JaggoRe AI
    • Email Extractor
    • TimeWell App
  • Login
  • Sign Up
  • Cart
Order Details

© 2026 - A learning platform by Odist Magazine

This website uses cookies. By continuing to use this website you are giving consent to cookies being used.
0