Keyed list rendering
The pattern
Render dynamic lists with <For> from @pyreon/core, keyed by a stable identifier via the by prop:
import { For } from '@pyreon/core'
<For each={todos()} by={(todo) => todo.id}>
{(todo) => <li>{() => todo.text}</li>}
</For>Key rules:
The keying prop is
by, notkey. JSX extractskeyat the VNode level for reconciliation of non-<For>elements; it never reaches the<For>runtime.byreceives the item and returns a unique key (usually a string or number).The render prop
{(item) => …}receives the item and returns a VNode. Signal reads inside track automatically.eachcan be a signal getter (todos()) or a signal directly (todos— compiler auto-calls).
Why
<For> runs the reconciler once per keyed diff — items that keep their key stay mounted across reorders, preserving DOM state (input focus, scroll position, animation) and avoiding remount work.
.map() in JSX produces a new array on every render and Pyreon has no way to reconcile — the entire list remounts on every update. That's fine for static arrays (one render) but catastrophic for signal-driven lists.
Anti-pattern
// BROKEN — remounts the full list on every update
<ul>{todos().map((t) => <li>{t.text}</li>)}</ul>// BROKEN — key is extracted by JSX, never reaches <For>
<For each={todos()} key={(t) => t.id}>
{(t) => <li>{t.text}</li>}
</For>// BROKEN — index-based key defeats the reconciler
<For each={todos()} by={(_, i) => i}>
{(t) => <li>{t.text}</li>}
</For>
// Reordering items keeps the same index-keys, so every item's DOM is
// reused for the WRONG logical item — focus and state scrambles.Related
Detector:
for-missing-by— fires on<For each>withoutbyDetector:
for-with-key— fires on<For key>(wrong prop name)Anti-pattern: "
keyon<For>" and "Missingbyon<For>" injsxcategoryReference API:
Forin@pyreon/core— seeget_api({ package: "core", symbol: "For" })