Implementing Optimistic UI Updates in React Applications for Better Perceived Performance

By : manish

Language: javascript

Date Published: 4 days, 3 hours ago

Optimistic UI updates improve perceived performance by updating the interface immediately when a user action occurs, assuming the server request will succeed. This technique is especially valuable in e‑commerce (e.g., adding items to a cart) or financial apps (e.g., transferring funds) where waiting for server responses can feel sluggish. If the request fails, you roll back the UI and show an error. Implementing this pattern involves: 1) mutating local state optimistically, 2) sending the request to the server, 3) reverting on failure or confirming on success. Libraries like React Query or SWR simplify this with built‑in mutate and rollback mechanisms, but you can also implement it manually with useState and useEffect. Key considerations include handling network errors, preventing race conditions, and providing clear undo or error messages. This pattern keeps the UI responsive and gives users confidence that their actions are immediate, even if background processes take time.


 

Style : Dark Mode : Line Number: Download Font Size:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// hooks/useOptimistic.js
import { useState, useCallback, useRef } from "react";

function useOptimistic(updateFn, onSuccess, onError) {
  const [optimisticValue, setOptimisticValue] = useState(null);
  const [isUpdating, setIsUpdating] = useState(false);
  const [error, setError] = useState(null);
  const latestValueRef = useRef(null);

  const applyOptimisticUpdate = useCallback((newValue) => {
    setOptimisticValue(newValue);
    setIsUpdating(true);
    setError(null);
    latestValueRef.current = newValue;
  }, []);

  const applyUpdate = useCallback(async (newValue) => {
    applyOptimisticUpdate(newValue);
    try {
      // Simulate API call - replace with actual fetch/mutation
      await simulateApiCall(newValue);
      onSuccess?.();
    } catch (err) {
      // Rollback to last known good value
      setOptimisticValue(latestValueRef.current);
      setError(err.message);
      onError?.(err);
    } finally {
      setIsUpdating(false);
    }
  }, [applyOptimisticUpdate, onSuccess, onError]);

  return {
    optimisticValue,
    isUpdating,
    error,
    applyUpdate,
  };
}

// Simulate API call (replace with real implementation)
function simulateApiCall(data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.1) resolve(data);
      else reject(new Error("Network error"));
    }, 800);
  });
}

// Usage example in a shopping cart component
// CartItem.jsx
import React from "react";
import useOptimistic from "../hooks/useOptimistic";

export default function CartItem({ item, onRemove }) {
  const {
    optimisticValue: cartItems,
    isUpdating,
    error,
    applyUpdate,
  } = useOptimistic(
    (newCartItems) => newCartItems, // identity update - we manage state in parent
    () => {}, // onSuccess
    (err) => console.error("Failed to update cart:", err) // onError
  );

  // In a real app, you'd pass a cart state setter from parent/context
  // Here we simulate with local state for demo
  const [cartItems, setCartItems] = React.useState([item]); // simplified

  const handleRemove = () => {
    const newCart = cartItems.filter((i) => i.id !== item.id);
    // Optimistically remove item
    setCartItems(newCart);
    applyUpdate(newCart); // triggers API call and handles rollback
  };

  return (
    <div className="cart-item">
      <span>{item.name}</span>
      <span>${item.price}</span>
      <button
        onClick={handleRemove}
        disabled={isUpdating}
        className={isUpdating ? "updating" : ""}
      >
        {isUpdating ? "Removing..." : "Remove"}
      </button>
      {error && <p className="error">{error}</p>}
    </div>
  );
}

// App.jsx - simplified demo
// <CartItem item={{id: 1, name: "Laptop", price: 999.99}} />
react optimistic-ui performance user-experience frontend
Login to see the comments