Ramonda

Conditional and filtered lists

A list is rarely the whole array. It shows up only when a panel is open, or it is the part of the data that matched a filter. Both fit list() naturally, because it is an expression, not a component.

star a row, then filter — a row that stays keeps its own star, and no row inherits one from a person the filter removed

  • Ada compilers
  • Grace runtimes
  • Edsger semantics
  • Barbara genomics
  • Katherine trajectories

Star a row, then narrow the filter. Every row that stays keeps its own star — none slides onto a neighbour, and none inherits the star of a row the filter removed.

(A row filtered out entirely is removed, so its star goes with it — bring it back and it returns fresh. That is not a lost update; it is a component that stopped existing and started again. list() protects the identity of the rows that stay.)

The filtered list is a derived value, not stored state

The tempting mistake is to keep a second array in state and update it from the search box — now every edit to the data has to remember to redo the filter, and a missed one shows stale rows with no error.

Instead, the filtered list is derived from the data and the query, so it is a @compute:

@state people: Person[] = [...];
@state query = "";

@compute get visible() {
  const q = this.query.trim().toLowerCase();
  if (!q) return this.people;
  return this.people.filter((p) => p.name.toLowerCase().includes(q));
}

One source of truth. visible recomputes when people or query changes and can never fall out of step, because it isn't stored anywhere to go stale.

Bind the list to the derived value

<ul>{list(this.visible, (item) => <PersonRow item={item} />)}</ul>

each is read as the list is built, so it is always the current filter. You don't declare a dependency on query — the read of this.visible in render is the dependency.

Why not this.visible.map(...)

Because filtering removes items from the middle, and a bare .map() matches survivors by position — so a row can end up sitting on a removed row's component, showing its star. list() matches by identity instead, so the survivors stay themselves. No key is needed, because filtering returns the same objects it selected from.

When the list isn't there at all

Same shape — it is a call in an expression slot, so a list that may never exist costs nothing until it does, and an empty result is just an empty array:

{this.query ? <ul>{list(this.visible, (item: Person) => <PersonRow item={item} />)}</ul> : null}
{this.visible.length === 0 ? <p>No matches.</p> : null}

Next

  • Hooks — reusable state and lifecycle with no element.