Forms & Mutations

Pending Mutations and Conflict Feedback


Learning Objectives

  • You can distinguish a pending proposal from an accepted server result.
  • You can preserve input after rejection without silently repeating a mutation.
  • You can block a stale edit until the user explicitly discards it and reloads.
  • You can explain why a lost response leaves the server outcome uncertain.

A network request takes time. Chapter 4.3 updated the parent-owned collection only after the API accepted a note. Continue that approach: show progress while waiting, preserve the proposed input after rejection, and display accepted state from the returned record.

Keep confirmed state visible while pending

A form can show “Saving…” and temporarily disable its submit button without claiming that the requested change already exists. Keep the last confirmed list, price, or status visible until the API returns an accepted result.

The pending guard in the submit handler also prevents a second submission while the first is unresolved. Clear that guard in finally, including when the operation rejects. An input being locally valid does not establish that a seat is available or that a publication is allowed.

Publishing an event

0 / 5 points

Publishing an event makes it visible to other users and may fail a server lifecycle rule. Which default feedback best communicates that boundary?

Figure 1 shows when the client can replace confirmed state: an accepted response supplies the new record, while a rejection leaves the proposed input available.

Figure 1. Pending feedback communicates progress; the API response establishes acceptance.

A network error can leave an unknown result

A request may reach the server, commit successfully, and lose its response. From the browser’s perspective, the promise rejects. From the server’s perspective, the change exists. Say that the operation could not be confirmed; do not claim that a failed connection rolled back the server’s transaction.

Aborting fetch stops the browser’s wait. It does not undo a committed database transaction. Blindly retrying a create operation can produce a duplicate unless the API implements an explicit mechanism for recognizing that repeated operation.

Return to the notes page to check stored state. Its form keeps the proposed title when a POST cannot be confirmed. A reload of /lab issues a fresh GET, but also resets local input. Use that only as a deliberate storage observation. A recovery control can read current data while keeping the form mounted and its input intact. Chapter 4.7 reproduces a lost accepted result and traces the actual request.

For bidding, keep the amount visible, explain that acceptance is unknown, and require a successful fresh read before another deliberate bid. A current price is an observation, not proof that this particular bidder’s previous request won.

Stop a stale edit until explicit discard and reload

A 409 STALE_VERSION is different from a lost response. The API rejected the edit because the record no longer has the expected version. Preserve the proposed text and block further saves. Do not automatically read a newer version or resend the old proposal with it.

For the core marketplace, offer one clear recovery action: Discard edits and reload. This action starts a read, not another edit. Replace the input with the current record only when that read succeeds. If it fails, keep the proposal, the save block, and the same recovery action. The user can copy their text before choosing to discard it.

Complete discard and reload

The walking skeleton supplies client/src/lib/components/DraftRecovery.svelte and the page client/src/routes/lab/recovery/+page.svelte. Keep /lab as the notes page; this activity runs at /lab/recovery. If your earlier download lacks these files, copy just these two files from the latest skeleton into practice-typed-http/.

The component supplies the save handler, stale guard, controls, pending cleanup, and error feedback. Its current value is the last confirmed record, title is the editable input, and stale blocks saving after a version conflict. Complete only the TODO in discardAndReload, using about three lines:

const discardAndReload = async () => {
if (pending) return;
pending = true;
message = "Reloading...";
try {
// TODO: Read the current record, replace the input, and release the stale guard.
message = "Current draft loaded. Previous edits discarded.";
} catch {
message = "Reload failed. Your input is still here; try discard and reload again.";
} finally {
pending = false;
}
};

Call and await the supplied readCurrent, store the returned record in current, replace the editable title, and release the stale guard. Both the replacement and the release must happen only after the read succeeds. Keep the supplied catch and finally behavior.

The page supplies controlled operations. Saving version 3 rejects with STALE_VERSION. The first call to readCurrent rejects; the next returns { title: "Updated maps", version: 4 }. You do not need to change those operations, add an API endpoint, or write tests for this ungraded activity.

At /lab/recovery, check the completed behavior:

  1. Change the title and save. The proposed input stays visible and Save edit becomes unavailable.
  2. Choose Discard edits and reload. The first read fails: keep the proposal, the save block, and the recovery button.
  3. Choose the same action again. The successful read replaces the input with Updated maps and enables saving.
  4. Edit the title and save. The operation now uses version 4 and shows the accepted title.

Reloading this practice page resets the controlled sequence. Its operations isolate the UI and do not modify the notes API. The distinction to preserve is that starting recovery does not complete it: only the successful read permits a new edit.

Milestone 22 applies the same rule to the real marketplace. Part 9 extends it with comparison and deliberate reapplication when studying resilience. Those additional states are not required for this core editor.

Check Your Understanding

  1. What can the interface truthfully show before a mutation is accepted?
  2. Why can a rejected network promise leave the server outcome unknown?
  3. Why must a stale edit remain blocked after a failed discard-and-reload read?