Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Svelte Props, Callbacks, and Keyed Editor Ownership

Last updated: 5 Oct 20268 min read
tutorial
IntermediateBy AITrove Editorial

A Svelte component receives data through $props. In a modern component contract, an ordinary callback prop can carry a child's command back to the owner. That is a different operation from changing an object prop in place. A parent that owns case status must decide when approval becomes pending, when the server result commits, and how a failure restores the prior value. Repeated rows also need stable identity: a keyed each block associates a view with a case ID when sorting, insertion, and removal change positions. The editor's private draft requires a separate lifetime rule. It may be stored by case ID if switching should preserve it, or reset intentionally when the selected case changes.

Working case

Reviewer 47 types a private note under permit 62. A new urgent case is inserted at the top of a list keyed by position, and the note appears beside permit 81 after view reuse. A child card also changes its received case status directly before the server responds. The approval fails, but no parent mutation record can reconcile the card and the summary. The repaired interface keys rows by stable case IDs, passes an onApprove callback that reports intent, and lets the parent own pending status. A draft map indexed by case ID keeps text attached to permit 62 through sorting; sign-out clears the map because those notes cannot belong to another reviewer.

Implementation boundary

svelte
<script lang="ts">
  type PermitSummary = { id: number; title: string; status: string };
  let { permitCase, onApprove }: {
    permitCase: PermitSummary;
    onApprove: (caseId: number) => void;
  } = $props();
</script>
<p>{permitCase.title}: {permitCase.status}</p>
<button type="button" onclick={() => onApprove(permitCase.id)}>Approve</button>
<!-- Parent: {#each visibleCases as permitCase (permitCase.id)} -->
<!--   <PermitCard {permitCase} onApprove={requestApproval} /> -->
<!-- {/each} -->

Type the case prop and callback prop so the child cannot quietly invent a different command payload. In the card, invoke onApprove with the stable ID and leave the record untouched. The parent allocates an operation ID, gates rapid duplicate clicks, sends the server request, and applies the returned status or rollback. Use the case ID as the each-block key, never the current row number or a new random value. For drafts that survive selection, put text in an owner-scoped map keyed by case ID. For drafts that should not survive, put the editor beneath a selected-case key boundary and check that focus reset is acceptable. Both choices need cleanup on successful submission, account switch, and expiry. A callback reports a request; it does not certify that a server mutation succeeded.

Cost and boundaries

A keyed list can retain the correct row views through reorderings and avoid tearing down every editor. That saves setup work but also preserves local state, so a draft map uses O(d) memory for d retained cases and needs an expiry policy. A pending-operation map uses O(p) for p in-flight approvals. Replacing a case in an array can take O(n) time; an indexed owner store can reduce lookup work but adds synchronization complexity. Keying an editor by selected ID recreates its component on each switch, which may discard unsent text and change focus. Measure those user-visible effects alongside render work, not only raw update time.

Failure trace

Start a draft for case 62, insert case 29, reverse sort, and assert the draft still belongs to 62. Filter 62 out and back in. If drafts are retained by ID, the text should return; if the contract discards them, an explicit notice should explain that choice. Reject an approval after the child reports intent and verify every display returns to the authoritative server state. Double click Approve while one operation is pending and inspect the server audit trail for one logical command. Remove a case before a late response arrives and ensure it cannot mutate a different row. Sign out, sign in as another reviewer, and verify the map is empty.

Verification

  • Child reports an ID but never mutates its record prop.
  • Row identity survives insertions and sort changes.
  • Private drafts and pending operations are removed at logout.

Practice drill

Build four permit cards for IDs 29, 47, 62, and 81. Pass a typed onApprove callback to each card and keep status in the queue owner. Insert case 17, sort descending, type a note, and switch the selected case twice. Reject one approval and then return a late response for a removed case. Record which ID owns each draft and pending operation after each step. Compare a keyed list with a deliberately position-keyed list to expose the failure, then keep only the stable-ID version. Finish with a logout cleanup test and a focus check for the editor lifetime you chose.

Decision note

Pass commands upward, keep server-backed state at its owner, and key repeated views by immutable case identity.

Common Mistakes

  • Keying a changing list by index.
  • Mutating an object prop to simulate a committed approval.
  • Retaining a reviewer draft map across account changes.

Related lessons

Svelte Reactivity and Server Boundaries; Svelte Runes: Source State, Derived Views, and Effect Cleanup; SvelteKit Form Actions, Validation, and Mutation Replay; SvelteKit Request-Scoped Load and Hydration State; React Component Identity and Keyed Draft Lifetime; Angular Component Input, Output, and Row Identity.

Apply and check

Build Project: Svelte permit review and approval action and review Web Development: Svelte reactivity and server boundaries quiz.

web-tech
web-development
Storage details