Summary#
on change { … } runs a block reactively — the runtime re-runs it whenever a value it reads changes. Use it to
push a value from the component to somewhere outside it. The canonical case is a page naming its own route:
component OrgEdit(string slug) {
var org = Organization.Single(o => o.Slug == slug);
on change { Navigation.SetTitle(org.Name); } // the tab + browser title track the org's name
}It belongs to the on <event> lifecycle-event family — on mount (setup, once), on change (a tracked reaction),
on unmount (teardown, once). See on mount / on unmount for the once-only siblings and The reactivity & lifecycle model for the whole
execution model.
Signature#
on change { <statements> } // re-runs whenever a reactive value it read changeson change takes no name — the on <event> family is anonymous. (Don't confuse it with an onChange input
prop: Input(value: x, onChange: Handler) is a field's change event, a different thing.)
Description#
What it's for#
An on change block is for outward work — a call whose result lands somewhere the component doesn't own:
| You want | Use |
|---|---|
| A computed value to render | live var name = expr; |
| Setup that runs once, when the page opens | on mount { … } (on mount / on unmount) |
| Teardown that runs once, when the page closes | on unmount { … } (on mount / on unmount) |
| To keep something outside the component in sync as data changes | on change { … } |
| To fetch again whenever a value it depends on changes | on change { results = Search(query); } |
An on change body has the same powers as an action — it can call server functions and reach the client verbs
(Navigation.*, Theme.*). The runtime calls it for you instead of a click.
It may write a field it does not read#
The block re-runs whenever a value it READ changes, so the fields it writes are its OUTPUT and the ones it reads are
its INPUT. Writing an output is how "fetch again when this changes" is spelled — query is read, names is written,
and typing into the box is what refetches:
entity Comet { string Name; }
[AllowAnonymous]
List<string> NamesLike(string q) => Comet.Where(c => c.Name.Contains(q)).Select(c => c.Name).ToList();
[Route("/comets")] [AllowAnonymous] [Render(CSR)]
component CometSearch() {
string query = "";
List<string> names = [];
on change { names = NamesLike(query); }
render {
Stack(gap: 2) {
Input(value: query, label: "Search");
foreach (var n in names) { Text(n); }
}
}
}Writing a field the block also READS is a compile error, because the write would wake the block again:
an
on changeblock must not writecountand also read it. That would loop: the reaction re-runs whenever a value it READ changes, so writing one wakes it again.
The rule follows the reads and writes behind an action or method the block calls, and the reads behind a live var
it reads; n++ both reads and writes, so it is always refused. If you need a value, use a live var computed; if you
need to set state once, use on mount; if a user gesture should set it, use an action.
When it runs#
It runs at mount (to establish its dependencies and do the initial sync), then again whenever any reactive value
it read changes — and only then. It is dependency-tracked: reading org.Name subscribes the block to org.Name,
so an unrelated change elsewhere on the page does not wake it. This is the whole point of the name — "on change" is
tracked-by-construction, where a block "that runs every render" would be waste. Triggers are any reactive read, not
only a live var: a plain state field reassigned by an action wakes it too. See The reactivity & lifecycle model.
Writing a body that is idempotent — safe to run again with the same inputs — is the contract; the platform
de-duplicates at the sink where it can (calling Navigation.SetTitle with an unchanged title does nothing).
Examples#
A create page whose title tracks the name as you type it — and falls back while the name is still blank:
entity Organization { string Name; }
[Route("/org/new")]
[Title("New organization")] // the static fallback (server-rendered, and before the reaction first runs)
[Render(CSR)]
component OrgCreate() {
Organization draft;
on mount { draft = new Organization {}; }
live var tabName = draft?.Name ?? "New organization";
on change { Navigation.SetTitle(tabName); } // the tab + browser title update as you type
render {
Stack(gap: 4) { Input(value: draft.Name, placeholder: "Organization name"); }
}
}See also#
- component — the component
on changelives in, and its other members. - on mount / on unmount —
on mount/on unmount, for setup and teardown that run once rather than reactively. - The reactivity & lifecycle model — the execution model: declarations vs
on mountvson changevson unmount, and how a change updates only the slots that read it. - Navigation —
Navigation.SetTitle, and the rest of the route surface anon changeblock can reach.