I don’t ship optimistic UI to make things “feel fast”; I ship it because the p95 round-trip to a different region is 120–250 ms and users will mash buttons if the UI stalls. The failure mode to design around isn’t slowness. It’s state drift: the client shows one thing while the server has another, and your next write explodes with 412 Precondition Failed or, worse, silently corrupts data.

How do you implement optimistic updates in React without state drift?

In production I keep the server as the authority. The client holds a queue of optimistic patches tagged with a unique mutationId and idempotency key. The server validates with a version/ETag and echoes the mutationId back. On ack, I commit the server snapshot; on reject, I rollback/rebase remaining patches. Never treat the optimistic cache as canonical.

That process sounds heavier than it is. You can assemble it from a few small parts: a version field (or ETag), unique mutation IDs, an idempotency key for the HTTP layer, and a state manager that can apply, rollback, and rebase patches. Components render derived state only.

Minimal React Query mutation that commits to the server’s snapshot