Model the two values explicitly
React's state-structure guidance warns against duplicate state that can disagree. A draft is an intentional exception: it represents pending input, while the canonical record represents server truth. Make their relationship explicit:
{
canonical: { id: "case-1", title: "Open", version: 7 },
draft: { title: "Open with customer", baseVersion: 7, dirty: true },
status: "editing" | "refresh_conflict" | "saved",
incoming: null | { id: "case-1", title: "Assigned", version: 8 }
}
If an incoming snapshot is newer and dirty is false, it can replace both the
canonical record and the draft base. If the draft is dirty, save the incoming
snapshot beside it and show that the record changed elsewhere. An older
snapshot must do nothing.
Keep optimistic display temporary
React's useOptimistic
provides a temporary UI projection during an Action. It does not decide which
server snapshot is authoritative, or merge two edits. Let the server response
or a documented refetch become the next canonical base after a successful
save.
The server should also reject stale writes under a documented contract. HTTP conditional-request semantics are one route described by RFC 9110; an explicit version field can serve the same purpose when the API defines it. The UI needs the current record and a visible conflict state when that precondition fails.
Give the person a deliberate next action
refresh_conflict should never look like ordinary success. Offer actions that
match the product's actual contract:
| Situation | Preserve | Useful action |
|---|---|---|
| Clean draft and newer snapshot | New server record | Continue editing |
| Dirty draft and newer snapshot | Draft and incoming record | Compare, copy, or reload |
| Save accepted | Returned server record | Continue from the new version |
| Save rejected as stale | Attempted draft and current record | Compare, revise, retry, or cancel |
The local P95 model tests all four state paths plus stale-snapshot rejection. It is a Node model, not a browser or live API test.
Keep the server boundary visible
Client state cannot authorize a save, lock a record, prove a request reached a server, or automatically merge arbitrary fields. It should not invent an order from timestamps whose semantics are unknown. The backend owns authentication, authorization, durable versioning, and conflict rules.
Stop before shipping if a background refresh can erase a dirty draft, an older snapshot can replace newer visible data, or a stale-save response cannot lead to a clear recovery path.