# Composition: context, callbacks, children & lists

How Rask components talk to each other — passing data down without prop drilling,
sending events up, nesting children, and rendering windowed or reorderable lists.

## On this page

- [Children & fragments](#children--fragments)
- [Component tiers: static method · stateless · stateful](#component-tiers-static-method--stateless--stateful)
- [Hosting a component you built yourself](#hosting-a-component-you-built-yourself)
- [Callbacks & context](https://rask.sh/docs/guides/composition-callbacks-context/) — child→parent callbacks (each runs for the component that wrote it), provide/consume context.
- [Lists, toasts, drag & error boundaries](https://rask.sh/docs/guides/composition-lists/) — virtualize, keyed lists, toasts, drag-and-drop, error boundaries.

---

## Children & fragments

Children attach through the **indexer**, not a `Children:` parameter:

```csharp
Div.Class("panel")[
    Span.Class("title")["Hello"],
    "plain text becomes a Text node",   // string → Text (HTML-encoded)
    items.Select(i => Li.Key(i.Id)[i.Name])
]
```

> **Keys must be unique among siblings.** `.Key(…)` is the reconciliation identity the live diff
> uses to move a row instead of rebuilding it. If two siblings share a key, keyed
> reconciliation can't tell them apart, so the diff falls back to a positional walk that may
> attach a row's DOM state (focus, input value, scroll) to the wrong sibling on reorder. The
> diff codec reports a one-time `data-rask-key="…"` error (via the diagnostics seam) when it detects
> a duplicate — treat it as a bug to fix, not noise.

> **For a component, `Key` also decides which instance is reused.**
> A keyed component is identified by its key rather than by its position among its siblings, which
> is what keeps the state it holds *itself* — a private field, an edit buffer, an open/closed
> toggle, a subscription taken in `OnMount` — with the item rather than with the slot when the list
> changes shape. Where on the chain you write it does not matter: steps written before `Key` are
> carried onto the instance the key keeps.
>
> ```csharp
> TodoRow.Key(item.Id).Item(item)   // reads best: which item, then what about it
> TodoRow.Item(item).Key(item.Id)   // the same thing
> ```
>
> **A component's key names one component of its type in the component that writes it** — a wider
> scope than an element's, which is its siblings. `Render()` builds a child, and runs its `Key` step,
> before the element it will sit under has been given its children, so the parent cannot ask "the same
> list?"; it asks "the same key, on the same type, written by me?". Two lists of the same row type in
> one component therefore need keys that differ between the lists as well:
>
> ```csharp
> Ul[recent.Select(o => OrderRow.Key($"recent-{o.Id}").Order(o))],
> Ol[pinned.Select(o => OrderRow.Key($"pinned-{o.Id}").Order(o))]
> ```
>
> A key written twice still gives each row an instance of its own and its own values — they are told
> apart by the order they are written in, so the state one holds moves to the other when the first
> stops being written. Rask reports it once, as a `Rask.Live` warning naming the component, the row
> type and the key. Keys in different components never meet: a row keyed `"a"` in a page and one keyed
> `"a"` in a component that page renders are two rows.
>
> **Children of the same type without a key keep their instance too**, by their order among the
> unkeyed ones: `Ui.Modal.Open(_confirming)` beside any number of keyed `Ui.Modal`s is the same modal
> on every render, however the keyed ones come, go or reorder around it. Key the ones that are items
> of a list; leave the fixed ones alone.
>
> **Without a key, a component is the *n*-th of its type that its owner writes.** The owner is the
> component whose `Render()` builds it: one sequence per type for the whole `Render()`, in the order it
> is written, whichever element each child ends up under (`UiInput<string>` and `UiInput<int>` are two
> types). A callout, an element, or a component of *another* type that comes or goes ahead of it
> therefore leaves it alone — its fields, what it loaded in `OnMount`, whether it is open. A child of
> its *own* type coming or going ahead of it does not: every later one of that type moves one place,
> and takes over the instance — and the state — of its neighbour. Nothing but a `Key` can tell those
> apart, so key a component that is written conditionally ahead of others of its type, just as you
> key the rows of a list. The same holds for a repeated key, and for the unkeyed children beside keyed
> ones: among themselves they are told apart by order alone.
>
> `Key` is available before a component's *required* steps too, generic ones included
> (`Ui.Select.Key(id).Value(v)`), so a row can say which item it is first. An element is re-specified
> in full every render, so its instance carries nothing and `Div.Class("line").Key(i)` never claims one.

A `[...]` collection expression renders its items with **no wrapping element** — use it for a list
of siblings. For the conditional "render nothing" branch, return `null` (a `null` child renders
nothing):

```csharp
show ? Panel() : null    // null renders nothing
```

> The `..` spread fails inside `[…]` (the compiler parses it as a `Range`). Pass the
> enumerable directly — `Div[items]` — instead of `Div[..items]`.

A layout is often a fragment for the same reason — several siblings that share no wrapper:

```csharp
protected override Component? Render() =>
    [Header[H1["My app"]], Main[Outlet], Footer["Contact"]];
```

The page root is an ordinary component too: it renders into `<body>`, and Rask composes the document
around it — see [the document and the `HeadAssets` override](https://rask.sh/docs/guides/getting-started/#7-the-document-and-the-headassets-override).

What a layout shows *about* the page inside it — the last breadcrumb, the tab title — is not passed down or
shared through a service: the page declares `PageTitle` and the layout reads `route.Title`, right in the
first HTML. See [A page's title](https://rask.sh/docs/guides/routing/#a-pages-title--pagetitle-and-routetitle).

---

## Hosting a component you built yourself

Components normally enter the tree through their **generated chain**, and that chain is what
registers the instance with its parent. Occasionally you can't call one — because the type isn't known
until runtime:

```csharp
var page = (Component)ActivatorUtilities.CreateInstance(services, pluginType);
```

Put it in the tree like any other child:

```csharp
Div.Class("host")[page]
```

It gets the whole lifecycle — `OnMount`, `OnUpdated`, `OnFirstRender`, `OnRendered`, `OnUnmount` — and a handle to re-render
through, exactly as a component a chain built does: the render walk notices an instance nothing registered and
registers it under the component whose subtree it sits in. Several instances of one type under one parent each
keep their own lifecycle.

> Constructing a component with `new` outside the framework is a compile error
> ([RASK014](https://rask.sh/docs/guides/diagnostics/#rask014)) — a type you can name, you build through its chain. Reflection-built
> instances, whose type the compiler never sees, are the case this is for.

---

## Component tiers: static method · stateless · stateful

There are **three ways** to author a reusable unit of UI, in ascending order of cost and
capability. Reach for the cheapest one that does the job.


**Tier 0 — a plain static method.** Just a function that returns markup:

```csharp
internal static class Tags
{
    public static Component Badge(string label) => Span.Class("pill")[label];
}
// call it like any method — Tags.Badge("new")
```

There is no `Component` instance, so it has **no state, no lifecycle hooks, and no independent
render cache** — its markup is inlined into whichever component calls it, on every render of that
caller. It is the leanest way to factor out repeated markup. Two things it *cannot* do: hold
mutable state, and safely consume ambient context — a static helper has no instance to carry the
"re-run me when context changes" latch, so a `Context.Get<T>()` inside one only refreshes when its
caller happens to re-render. The moment you need either, promote to Tier 1.

**Tier 1 — a stateless component.** A `Component` subclass whose `Render()` is a pure function of
its props, with **no mutable fields**:

```csharp
public sealed partial class Greeting : Component
{
    public required string Name { get; set; }   // non-nullable, no initializer → a required chain step
    protected override Component? Render() => P["Hello, ", Strong[Name], "!"];
}
// name it and chain — Greeting.Name("Ada")
```

Public settable props become chain steps and setters (see the rules in
[the README](https://github.com/pal-tamas/rask/blob/main/README.md) and [lifecycle.md](https://rask.sh/docs/guides/lifecycle/)). Over a static method it gains a
reconciliation identity, the [lifecycle hooks](https://rask.sh/docs/guides/lifecycle/), render caching, `<head>`
contribution, and safe context reads — it simply carries no local state.

**Tier 2 — a stateful component.** A `Component` subclass that keeps local state in **private
fields** and mutates them in handlers:

```csharp
public sealed partial class Counter : Component
{
    private int _count;
    protected override Component? Render() =>
        Button.OnClick(() => _count++)[$"Clicked {_count} times"];
}
```

The instance persists across renders (Rask reconciles it by its type and its order among the children
of that type its owner writes, or by an explicit `Key` — see
[children](#children--fragments)), so the field survives. The `OnClick` lambda captures `this`, so after it runs the
framework re-renders this component **automatically — no `StateHasChanged()`** (the same auto-wrap
that powers [callbacks](https://rask.sh/docs/guides/composition-callbacks-context/#callbacks-child--parent)). You only call `StateHasChanged()` when the
mutation happens *off* the event-dispatch path (e.g. a background poll loop — see
[lifecycle.md](https://rask.sh/docs/guides/lifecycle/)).

| Tier | You write | State | Lifecycle | Render cache | Chain | Context reads |
|------|-----------|-------|-----------|--------------|---------|----------------|
| **0 · static method** | `static Component Foo(…)` | none (inlined) | none | none | none — call it | ⚠️ only refreshes with the caller |
| **1 · stateless component** | subclass, props → `Render()`, no fields | none | ✅ | ✅ | ✅ generated | ✅ |
| **2 · stateful component** | subclass with private fields | private fields | ✅ | ✅ | ✅ generated | ✅ |

**Rule of thumb:** start with a static method; promote to a stateless component when you need an
identity, lifecycle, `<head>` assets, context-driven re-render, or a clean chain API; promote to
a stateful component when you need mutable local state.

---

See also: [Lifecycle](https://rask.sh/docs/guides/lifecycle/) for when `OnUpdated` refires, and
[JS interop](https://rask.sh/docs/guides/js-interop/) for element refs and scoped JS.
