Iterates over a reactive list and calls fn for each item. The
callback receives a per-item callable (a mini-store for record items,
a scalar accessor for atomic items) and an optional position
accessor. The reconciliation strategy is selected by by:
Arguments
- items
A reactive expression that returns a list.
- fn
A function of
(item),(item, pos), or().itemis the per-item callable (mini-store or scalar accessor);posis a 0-arg reactive accessor returning the item's current 1-indexed slot.posis constant underby = NULL(positional identity) and live underby = fn(fires on reorder).- by
NULLfor positional reconciliation, or a function that extracts a unique comparable key from each item for keyed reconciliation.
Details
Positional (
by = NULL, the default) — slot i is slot i. The list can grow or shrink at the end; in-place value changes update each slot's accessor without DOM recreation.Keyed (
by = \(x) x$id) — items are tracked across reorders, adds, and removes by their key. Kept items propagate new values through their mini-store (only changed leaves fire); reordered items have their DOM nodes moved (no recreation).
Records are projected as a per-item mini-store: item() reads the
whole record, item(record) writes it back, item$field() reads a
leaf, and item$field(v) is a synthetic setter that writes through
the parent. Scalars are passed as a per-item callable: item() reads,
item(value) writes back to the parent's slot.
Records may be heterogeneous in shape — different leaf trees per
entry. Each slot's mini-store is sized to its own item, and a
Match() inside the body dispatches on the discriminator:
Each(state$blocks, by = \(b) b$id, \(block) {
Match(block,
Case(\(b) b$type == "heading", \(b) Heading(b)),
Case(\(b) b$type == "paragraph", \(b) Paragraph(b)),
Case(\(b) b$type == "todo", \(b) Todo(b))
)
})When a record's shape changes (different key set, or a sub-record's shape shifts), that one entry is torn down and rebuilt with the new mini-store. Shape-stable updates use the fine-grained in-place path.
Mixing records and scalars in the same list is rejected at flush
time as a likely data-modeling slip — wrap scalars in
list(value = ...) to mix them.
