Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

on change

on change { … } — run a block whenever a reactive value it reads changes, to push that value somewhere outside the component

`on change { … }` is a reactive **side-effect**: the runtime re-runs it whenever a value it read changes, so it's how you keep something OUTSIDE the component in sync with something inside it — most commonly the page's title (`on change { Navigation.SetTitle(org.Name); }`) — or to refetch when a value changes (`on change { results = Search(query); }`). It may write a field of its own that it does not read; writing one it reads would loop, and is a compile error.

stable2 examples compiled by CIuiauthoringreactivity

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 changes

on 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 wantUse
A computed value to renderlive var name = expr;
Setup that runs once, when the page openson mount { … } (on mount / on unmount)
Teardown that runs once, when the page closeson unmount { … } (on mount / on unmount)
To keep something outside the component in sync as data changeson change { … }
To fetch again whenever a value it depends on changeson 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 change block must not write count and 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 change lives 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 mount vs on change vs on unmount, and how a change updates only the slots that read it.
  • Navigation — Navigation.SetTitle, and the rest of the route surface an on change block can reach.

Related

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…

on mount / on unmount

`on mount { … }` runs a block ONCE, the first time a component appears, beside its first reads; `on unmount { … }` runs…

The reactivity & lifecycle model

How an Osy# component comes alive and stays in sync: declarations are live value bindings, `on mount`/`on unmount` are…

Navigation

The routes the user has open, which of them hold UNSAVED edits, and the verbs that move between them or close one…