Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
Shipping an optimistic update without a rollback is not a faster version of the pessimistic pattern — it is a correctness bug. When the server rejects a mutation, the UI now shows a state that does not exist on the server. The user has no way to know what actually happened: did the action succeed? Is the UI lying? Over time, these silent failures erode trust. The rollback requirement is not optional; it is the contract that makes optimism safe. Implementing it correctly means capturing the pre-mutation state before calling setState, not after, and restoring it unconditionally in the catch block, regardless of how many times the user has clicked in the interim.
A like button with a 20% random failure rate. The button reverts its visual state and shows an error toast when the server rejects the mutation, making the rollback behavior explicit and observable.
1.0 (always fail) and click the button ten times — confirm the count returns to exactly 42 after each click and never drifts.const prevLiked = liked to after setLiked(!liked) and observe that the rollback now stores the wrong value — verify by checking what the button reverts to after a failure.console.log('rolling back from', !prevLiked, 'to', prevLiked) in the catch block and verify the logged values match the visible button state after each failed click.handleLike() again — wire it using the current liked state so retrying re-enters the full optimistic flow.import { useState } from "react";
async function likePost(id: string, liked: boolean): Promise<void> {
await new Promise((r) => setTimeout(r, 500));
if (Math.random() < 0.2) throw new Error("Rate limited");
}
export function LikeWithRollback({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
const [count, setCount] = useState(42);
const [toast, setToast] = useState<string | null>(null);
async function handleLike() {
const prevLiked = liked;
const prevCount = count;
// capture before mutating
setLiked(!prevLiked);
setCount((c) => (prevLiked ? c - 1 : c + 1));
setToast(null);
try {
await likePost(postId, !prevLiked);
} catch {
// restore exactly what we captured
setLiked(prevLiked);
setCount(prevCount);
setToast("Couldn't save your like. Try again.");
}
}
return (
<div>
<button onClick={handleLike}>
{liked ? "❤️" : "🤍"} {count}
</button>
{toast && (
<div role="alert" style={{ background: "#fee", padding: 8, marginTop: 8 }}>
{toast}
</div>
)}
</div>
);
}