Optimistic UI Updates
In this page:
Updating the UI Before the Server Confirms
In a normal flow, you'd wait for a server response before updating the UI, which can feel sluggish for small, usually-successful actions. An optimistic update flips this: change the local state immediately, assuming success, then quietly fire off the real request in the background.
Note: Optimistic updates work best for actions that rarely fail, like liking a post or toggling a checkbox — the risk of a visible revert is low.
Warning: For actions with a meaningful chance of failure (like a payment), the sudden visual revert on failure can be more jarring than just waiting for confirmation upfront.
Example: Updating the UI Before the Server Confirms
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const [liked, setLiked] = React.useState(false);
const handleLike = () => {
setLiked(true); // update immediately
// fetch('/api/like', { method: 'POST' }); // fires in background
};
return <button onClick={handleLike}>{liked ? "Liked!" : "Like"}</button>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Reverting on Failure
Since the optimistic update assumes success, you need a plan for when the real request fails: storing the previous value before changing it lets you roll the UI back to that exact prior state if the server responds with an error.
Note: Capture the previous value in a local variable right before applying the optimistic change, so it's available in the catch block.
Warning: Skipping the revert step means a failed request leaves the UI silently lying about the true server state.
Example: Reverting on Failure
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const [liked, setLiked] = React.useState(false);
const handleLike = () => {
const previous = liked;
setLiked(true);
const requestFailed = true; // simulate a failed request
if (requestFailed) setLiked(previous);
};
return <button onClick={handleLike}>{liked ? "Liked!" : "Like (will revert)"}</button>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Optimistic Updates for Lists
The same idea extends to adding or removing items from a list: add the new item to local state immediately, send the real request in the background, and remove it again (or show an error) if the request ultimately fails.
Note: Give optimistically-added items a temporary local ID, replacing it with the real server-assigned ID once the request succeeds.
Warning: Removing the wrong item during a revert (if IDs aren't tracked carefully) can silently delete a different, unrelated item from the list.
Example: Optimistic Updates for Lists
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const [items, setItems] = React.useState(["First item"]);
const addItem = () => {
setItems(prev => [...prev, "New item (optimistic)"]);
// fetch('/api/items', { method: 'POST' }).catch(() => {
// setItems(prev => prev.slice(0, -1)); // revert on failure
// });
};
return <div><button onClick={addItem}>Add Item</button><ul>{items.map((i, idx) => <li key={idx}>{i}</li>)}</ul></div>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Not reverting the optimistic change if the actual server request fails, leaving the UI permanently showing incorrect data.
- Applying the optimistic update after the request instead of before, defeating the whole point of feeling instant.
- Not giving any visual cue that an update is pending confirmation, if that matters for the use case.
- An optimistic update changes the UI immediately, assuming a request will succeed, before waiting for the server's response.
- This makes actions like liking a post or checking off a to-do feel instant rather than sluggish.
- If the actual request fails, the UI must be rolled back to its previous state.
- Storing the previous value before updating makes reverting straightforward if needed.
No React-version restriction — a UI pattern built from useState and standard request handling.
Chapter Quiz — Complete all 10 topics to unlock
0/10 topics done
Complete these topics first: