Implementing Optimistic UI Updates in React Applications for Better Perceived Performance
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.
💻Source Code
// 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}} />🔗Related Snippets
Similar code snippets you might find interesting
Using Intersection Observer for Lazy Loading and Scroll-Based Animations
Leveraging Astro Islands for Interactive Content-Heavy Sites
Maintaining a Simple Global State Store in React Without Extra Libraries
💬Comments (0)
🔒Please login to post comments
No comments yet. Be the first to share your thoughts!
⚡Actions
Share this snippet:
👤About the Author
manish
Active contributor
