위치 대신 ID로 선택하기
Selection By Stable Identity
Keep selection attached to an entity while a collection reorders or refreshes.
이 자료로 구현하기
원문 주소와 버전이 포함된 요청문입니다. 복사한 뒤 만들 화면과 요구사항을 채워 에이전트에게 전달하세요.
FROM PRINCIPLE TO PRACTICE
이 원리로 만든 예제 2
예제를 열어 직접 조작하고, 관찰한 동작을 이 문서의 원리와 비교해 보세요.
MultiSelect Form · Ved UI
- 선택 ID와 표시 객체의 변환
MultiSelectForm은 캐릭터의 value 문자열 배열을 frameworks 필드에 저장합니다. 표시용 Option은 characters.filter로 다시 만들고 onChange는 Option에서 value만 추출합니다. 따라서 표시 객체의 순서는 캐릭터 목록의 순서를 따르며 레이블 자체가 제출 값은 아닙니다.
- 선택 ID와 표시 객체의 변환
문자 조합·검색 순서·선택 IDInput and async selection
- 조합 입력과 비동기 결과를 분리
결과를 필터링해도 선택한 문서 ID를 보존하고 인덱스로 선택을 바꾸지 않습니다.
- 조합 입력과 비동기 결과를 분리
Repository Boundary
Keep selection attached to an entity while a collection reorders or refreshes. This pattern belongs to Design Engineering and adds no Layout CSS or shared store implementation.
Reusable Method
Use for lists, tables, and detail views with stable IDs. Index selection is sufficient only for immutable position-based options whose identity truly is their position.
State Model And Ownership
The screen owns selectedId; the data owner supplies current entities. Detail is derived by ID. Missing from a filtered page does not imply deletion: choose a retained-detail fetch or an unavailable state based on actual knowledge.
Transitions
| Event and precondition | Next state | Effect or invariant |
|---|---|---|
| Select an available ID | ID selected | Resolve current entity by identity |
| Collection reorders | Same ID | Detail still belongs to the same entity |
| Authoritative deletion removes ID | Null or unavailable | Apply the declared fallback and recover focus |
Minimal Executable Example
Run this standalone JavaScript block with Node.js 22 or newer, or run all pattern examples from a repository checkout with bun run test:state-management. The assertions exercise the local model, not a browser or backend.
import assert from "node:assert/strict";
let rows = [{ id: "a", name: "Alpha" }, { id: "b", name: "Beta" }];
let selectedId = "b";
const selected = () => rows.find(row => row.id === selectedId) ?? null;
rows = [...rows].reverse();
assert.equal(selected().name, "Beta");
rows = rows.map(row => row.id === "b" ? { ...row, name: "Updated" } : row);
assert.equal(selected().name, "Updated");
rows = rows.filter(row => row.id !== "b");
if (!selected()) selectedId = null; // This collection is authoritative and complete.
assert.equal(selected(), null);What Breaks If Removed
Keep row index 1, reverse the array, and the detail silently changes to another entity. Keep a copied selected object and a refresh can leave its fields stale.
Composition And Substitution
Use URL state when selection should survive links and history. Use latest request wins for separately fetched detail; key requests by selected ID.
Opinionated Guidance
Choose an explicit missing-item policy. Never infer remote deletion solely from pagination or filtering.
Platform-Specific Guidance
After deleting a focused row, move focus to a logical neighbor or heading. A selected state and keyboard focus are separate responsibilities.
Unsupported Absolutes
Stable keys do not prove stable identity if the server recycles identifiers across tenants or revisions.
Verification Contract
- Reorder and update the selected row; detail must preserve ID and refresh fields.
- Filter it out without deleting it; apply the declared retained-detail policy.
- Delete the final item; verify empty detail and focus recovery in the UI.
Record actual outcomes separately using the verification matrix.
Source, License, And Attribution
Locally authored synthesis and examples. No upstream implementation is copied. These experimental contracts describe consumer-owned behavior; review when a failure, ownership change, or platform change invalidates an assumption.
IA Navigation
Parent: State Management. Next: Continue the state management route.


