[]previous and next page

Lists

For renders one row per item and moves rows instead of rebuilding them.

For agentsUpdated
Show the code
Rows.tsxtsx
import { useSignal } from "@shadow-js/core";
import { For } from "@shadow-js/web";

const names = ["Ada", "Grace", "Linus", "Margaret", "Alan"];

export function Rows() {
  const [rows, setRows] = useSignal(names);
  const shuffle = () => setRows([...rows()].sort(() => Math.random() - 0.5));

  return (
    <div class="stack">
      <For each={rows()}>{(name) => <input placeholder={name} aria-label={name} />}</For>
      <button type="button" onClick={shuffle}>
        Shuffle rows
      </button>
    </div>
  );
}

Type something in a row, then shuffle. The row moves and your text goes with it: For kept each row's DOM and its input, and moved them. It matched rows to items by identity, so the same string is the same row.

Items that are objects#

When your data is replaced by fresh copies, a server response or an immutable update, identity changes on every update and every row would rebuild. Give For a key and rows are matched by that value instead. The row function then receives the item as an accessor:

Show the code
Todos.tsxtsx
import { useSignal } from "@shadow-js/core";
import { For } from "@shadow-js/web";

interface Todo {
  id: number;
  title: string;
}

export function Todos() {
  const [todos, setTodos] = useSignal<Todo[]>([
    { id: 1, title: "Write a doc" },
    { id: 2, title: "Feed the cache" },
  ]);
  let next = 3;

  return (
    <div class="stack">
      <button type="button" onClick={() => setTodos((list) => [...list, { id: next, title: `Todo ${next++}` }])}>
        Add a todo
      </button>
      <ul>
        <For each={todos()} key={(todo) => todo.id}>
          {(todo) => (
            <li>
              {todo().title}{" "}
              <button type="button" onClick={() => setTodos((list) => list.filter((t) => t.id !== todo().id))}>
                Remove
              </button>
            </li>
          )}
        </For>
      </ul>
    </div>
  );
}
You write Rows are matched by The row function gets
<For each={list}> The item itself (===) The item, and an index accessor
<For each={list} key={(item) => item.id}> key(item) An accessor of the item, and an index accessor
<Index each={list}> The position An accessor of the item, and the index

Keys must be unique. A repeated key still renders every item, but the later copies get a fresh row on every update, and development builds warn.

Go deeper#

Lists and keys shows how rows move, what happens to focus, empty lists with a fallback, stores with arrays and constant-time selection.