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.
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}} /> |