Skip to contents

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:

Usage

Each(items, fn, by = NULL)

Arguments

items

A reactive expression that returns a list.

fn

A function of (item), (item, pos), or (). item is the per-item callable (mini-store or scalar accessor); pos is a 0-arg reactive accessor returning the item's current 1-indexed slot. pos is constant under by = NULL (positional identity) and live under by = fn (fires on reorder).

by

NULL for positional reconciliation, or a function that extracts a unique comparable key from each item for keyed reconciliation.

Value

A irid control-flow node.

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.