Define the client-server contract
The optimistic row needs this state:
{
clientId: "client-row-1",
operationId: "create-operation-1",
serverId: null,
serverVersion: null,
title: "Draft title",
status: "creating",
pendingEdit: null
}
The identities are deliberately separate:
clientIdis generated once when the interface creates the row and remains its React key.operationIdidentifies one logical create attempt and is reused after a timeout or lost response.serverIdis the canonical record identity returned by the backend.serverVersionprotects a later edit from silently overwriting newer state.
React's list guidance says keys need
to be stable and come from the data rather than be generated while rendering.
Do not replace the row key when serverId arrives. Store the server ID as data
on the same client row.
Make create replay an explicit backend rule
HTTP does not make POST inherently idempotent. RFC 9110 classifies POST differently from idempotent methods such as PUT. If the API uses POST for creation, define replay behavior in the application contract.
The P67 fixture stores the first successful response under operationId:
const prior = operations.get(operationId);
if (prior && prior.request.title === title) return prior.response;
if (prior) return conflict("operation_payload_changed");
const record = insertOneRecord(title);
operations.set(operationId, { request: { title }, response: record });
return record;
In a real service, the operation record and durable create need an atomic uniqueness boundary. An in-memory map proves only the state rule. Define retention, authentication, authorization, tenant scope, and behavior when the same operation ID arrives with changed input.
Never generate a new operation ID merely because the client did not receive a response. A timeout means “outcome unknown,” not “the server definitely did nothing.”
Queue the immediate edit
Let the user edit the optimistic row while creation is pending. Update the visible title and store the latest pending edit, but do not invent a server URL or send an update without a canonical record ID.
When create settles:
- attach
serverIdandserverVersionto the existing client row; - keep
clientIdunchanged; - send the pending edit to the canonical server record;
- include the expected server version; and
- mark the row confirmed only after the edit succeeds.
The current React state-structure guidance recommends avoiding duplicate and contradictory state. Keep one row state machine rather than independent “temporary row,” “saved row,” and “editing row” collections that must be reconciled later.
If multiple edits occur before create returns, the smallest policy is usually to retain the latest desired field state. Workflows that must preserve every intermediate action need an ordered command log and a different contract.
Show pending, retryable, and conflicted states
Use explicit interface states:
| Status | Meaning | Available action |
|---|---|---|
creating |
The create outcome has not arrived | Continue bounded local editing; prevent destructive dependent actions |
retryable |
Failure or lost response left the outcome unresolved | Retry with the same operation ID |
confirmed |
Server identity and latest accepted version are known | Send version-aware edits |
conflicted |
The create payload changed or the server version advanced | Show current server state and ask the user to reconcile |
React's useOptimistic documentation
shows how optimistic list items can carry pending state until canonical data
arrives. That API controls what React renders; it does not define operation
identity or database uniqueness.
Announce status changes in adjacent text or an appropriate status region. Keep the row keyboard reachable. Do not remove the user's edited text after a retryable failure, and do not announce success until the server result is known.
The JSX adapter is environment-specific and was not browser-tested for this unit. The tested evidence is the framework-independent state contract.
Reproduce the seven tested cases
Run:
npm test --prefix sites/reactjsx.com/evidence/P67
On Node.js 22.18.0, seven tests passed with no failures:
- An edit made before the create response is queued and produces one record.
- Replaying one create operation returns the same record.
- A lost response can be retried without creating a duplicate.
- Reusing an operation ID with changed input returns a conflict.
- A transient create failure remains retryable with the same operation.
- A stale edit becomes a visible conflict rather than overwriting newer data.
- The client row identity remains stable when the server ID arrives.
The fixture is synchronous and in memory. It does not test React rendering, network races, database transactions, multiple processes, authentication, authorization, or assistive technology. Preserve those limitations when adapting the pattern.
Verification checklist
- The optimistic row receives one stable client ID before rendering.
- One logical create retains the same operation ID across retries.
- The backend atomically replays a prior successful create response.
- Changed input under the same operation ID is rejected.
- Edits wait for the canonical server ID.
- The client row key does not change after confirmation.
- Edits use a server version or equivalent conflict rule.
- Pending, retryable, confirmed, and conflicted states are visible.
- A lost-response test proves that only one durable record exists.
Frequently asked questions
Can I use the server ID as the React key?
Use it after the record exists if the row was not already rendered. For an optimistic row, keep a stable client key so confirmation does not replace the component and lose local state.
Does useOptimistic prevent duplicate API requests?
No. It helps render optimistic state. The backend operation contract and durable uniqueness rule prevent duplicate records.
Should the interface block editing until create finishes?
That is a valid simpler design. If immediate editing is valuable, queue the edit under the stable client row and apply it only after canonical identity arrives.