
# Components

The page now does two jobs in one function: it renders rows and it handles a form. As it grows, splitting it keeps each part readable. A component is a function, so splitting is moving code into another function.

First add a `remove` function next to `toggle` and `add`, so a row can delete its item:

```ts
// The packing list's state and every way it changes.
import { signal } from "zerolith";

export interface Item
{
    id: number;
    name: string;
    packed: boolean;
}

export const items = signal<Item[]>([
    { id: 1, name: "Passport", packed: true },
    { id: 2, name: "Phone charger", packed: false },
    { id: 3, name: "Rain jacket", packed: false }
], { name: "items" });

export function toggle(id: number): void
{
    items.set(items.get().map((item) =>
        (item.id === id ? { ...item, packed: !item.packed } : item)));
}

export function add(name: string): void
{
    const id = Math.max(0, ...items.get().map((item) => item.id)) + 1;
    items.set([...items.get(), { id, name, packed: false }]);
}

export function remove(id: number): void
{
    items.set(items.get().filter((item) => item.id !== id));
}
```

## A row

Create `src/components/item.ts`:

```ts
// One row of the list. It runs once per item; its bindings follow the item.
import { type ReadableSignal, type View, computed, html } from "zerolith";
import { type Item, remove, toggle } from "../store.ts";

export function ItemRow({ item }: { item: ReadableSignal<Item> }): View
{
    const name = computed(() => item.get().name);
    const packed = computed(() => item.get().packed);
    return html`
        <li>
            <label>
                <input type="checkbox" .checked=${packed}
                    @change=${() => toggle(item.get().id)}>
                ${name}
            </label>
            <button type="button" aria-label="Remove ${name}"
                @click=${() => remove(item.get().id)}>Remove</button>
        </li>`;
}
```

Props are an object, and TypeScript checks them like any function's arguments. The row receives `item` as a signal, the same signal `each` hands to its row function, so the row's bindings follow the item.

The remove button has visible text and an `aria-label` that names the item, because "Remove" on its own does not say which item a screen reader user is about to remove. An attribute can mix text and bindings: `aria-label="Remove ${name}"` stays current if the name changes.

## The form

Create `src/components/add-item.ts`, with the form from the last step:

```ts
// The form that adds an item, with the field it clears after each one.
import { type View, html } from "zerolith";
import { add } from "../store.ts";

export function AddItem(): View
{
    const submitted = (event: SubmitEvent): void =>
    {
        event.preventDefault();
        const form = event.currentTarget as HTMLFormElement;
        const name = String(new FormData(form).get("name") ?? "").trim();
        if (name !== "")
        {
            add(name);
            form.reset();
        }
    };
    return html`
        <form @submit=${submitted}>
            <label>New item
                <input name="name" autocomplete="off" required>
            </label>
            <button>Add</button>
        </form>`;
}
```

## The page, from its parts

Now `src/components/packing.ts` puts the parts together:

```ts
// The packing list page, put together from its parts.
import { type View, computed, each, html } from "zerolith";
import { items } from "../store.ts";
import { AddItem } from "./add-item.ts";
import { ItemRow } from "./item.ts";

export function Packing(): View
{
    const count = computed(() => items.get().length);
    return html`
        <h1>Packing list</h1>
        <p>${count} items</p>
        <ul>${each(items, (item) => item.id, (item) => ItemRow({ item }))}</ul>
        ${AddItem()}`;
}
```

A component call goes straight into a slot: `${AddItem()}`. Call the function; passing `AddItem` without calling it is reported as `unrenderable_value`, since a function is not something a page can show.

## Why components run once

Each of these functions runs one time, when its part of the page is created. `ItemRow` runs once per item, not once per change: checking an item updates the row's checkbox and nothing else. This is why it is safe to create signals and computed values at the top of a component, and why there are no rules about the order of hooks or dependency arrays to remember: there is nothing to re-run.

## What you learned

- Splitting a component is moving code into another function and calling it.
- Props are an object; a row gets its item as a signal.
- Components run once, and their bindings keep them current.

