Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / UI

on mount / on unmount

on mount { … } / on unmount { … } / on frame (double dt) { … } — run a block once when the component appears, once when it goes away, and once per displayed frame

`on mount { … }` runs a block ONCE, the first time a component appears — before its first paint; `on unmount { … }` runs a block ONCE when it goes away — after its children tear down. Both have the full power of an action (set state, create data with `new Entity{}`, call a server function): `on mount` sets a page up, `on unmount` tears it down.

stable2 examples compiled by CIuiauthoringlifecycle

Summary#

on mount { … } and on unmount { … } are component lifecycle hooks — the bookends of an instance's life. Each is an auto-invoked action: the runtime calls it for you instead of a click, so each can do everything an action can — assign a field, create rows with new Entity{ … }, call server functions. They belong to the on <event> family alongside the reactive on change block.

  • on mount runs once, when the component first mounts, before its first render. Its effects are in place for the first paint.
  • on unmount runs once, when the component goes away, after its children have torn down. Use it for a final flush or an explicit release.
  • on frame (double dt) runs once per displayed frame, for as long as the component is mounted. It is the odd one out and the only phase that repeats — see running code every frame.
component OrgCreatePage() {
  Organization draft;                          // no initializer — null until mount
  on mount { draft = new Organization {}; }    // seed a fresh draft to bind the form to
  on unmount { Log.Information("editor closed"); }    // a parting side-effect
  render { Input(value: draft.Name); … }
}

Signature#

on mount   { <statements> }   // runs once, at first mount, before the first render
on unmount { <statements> }   // runs once, at teardown, after children tear down
on frame (double dt) { … }    // runs once per displayed frame; `dt` is the seconds since the last one
T name;                       // a field with NO initializer defaults to null (a bare C# field)

Both are anonymous (the on <event> family carries no name), and a component may declare at most one of each.

Description#

Use on mount for the setup a page needs the moment it opens, and on unmount for the teardown it owes when it leaves:

  • Runs once, per instance. on mount fires when a component instance first mounts; on unmount fires when that same instance is disposed. Switching to a retained tab and back is the same instance, so neither re-fires; a reload or reopening the page is a new instance, which mounts (and later unmounts) again.
  • on mount is before the first paint. State it sets and rows it creates are reflected in the first render — no flash of an empty form.
  • on unmount is after the children. Teardown runs deepest-first: a child region's on unmount fires before its parent's, and a page's own live queries and reactions are disposed automatically as its scope goes away — you never hand-unsubscribe. on unmount is for the extra teardown only your code knows about.
  • Full action powers. Read/write a field, new Entity{ … } (into the page's overlay), call a server function. It's the same body a user action runs.

A companion: a field's initializer is optional (D64). Organization draft; declares the field with no value (null), exactly like a C# field — so on mount (or an action) can fill it in later. This is what lets an edit form and a create form look the same: the edit page loads its row into a server read, the create page makes one in on mount, and both bind their inputs to that one entity.

Creating vs. editing — the same shape. Because a created row lives in the page's overlay just like an edited one, a create page is dirty-tracked exactly like an edit page: a freshly-seeded, untouched draft is not dirty (closing the tab discards nothing and prompts nothing), and it becomes dirty the moment you type. UnitOfWork.Commit() on a Save action persists the draft.

What these are not (yet). The lifecycle family is on mount / on unmount / on frame; on show/activate and on route-change are not built yet. Both run client-side (a server-rendered page paints without on mount, then runs it on hydration). A block that reads a server-read field which hasn't loaded yet sees the loading state, the same as an action would. These are known limits, not bugs.

Running code every frame — on frame (double dt)#

on frame (double dt) runs once per displayed frame — roughly sixty times a second — and is the only lifecycle phase that repeats and the only one that takes a parameter. It is what a game loop, a simulation or a physics step is written in, and it is the clock a Canvas is drawn on.

It is not a reaction. on change re-runs when a value it read changes; a frame body is driven by time and typically reads nothing that changes on its own, so wiring it as a reaction would either never re-run or spin.

on frame (double dt) {
  elapsed = elapsed + dt;             // `dt` is SECONDS since the previous frame
  x = x + speed * dt;                 // scale by dt, and the motion is frame-rate independent
}

Five things about it are worth knowing before you write one:

  • dt is required, and it is seconds. A body that does not scale by elapsed time runs at whatever speed the display happens to be — the defining bug of a hand-rolled loop, and invisible on the machine it was written on. You name the parameter; dt is the convention, not the contract.
  • It is clamped at 100ms. Return to a backgrounded tab, or hit a long pause, and the real gap can be seconds; an unclamped dt would teleport everything through walls in one step. Past 100ms the loop runs slow rather than wrong — so a body cannot measure its own frame rate below 10fps from dt. Use DateTime.UtcNow if you need the true elapsed time.
  • It pauses while the tab is hidden, and the first frame back is an ordinary one rather than one carrying the whole absence.
  • It never re-enters. The next frame is requested only after the body returns, so a slow body slows the loop down instead of queueing copies of itself.
  • A body that throws stops the loop, loudly, and logs the error with its stack. Sixty identical errors a second would bury the first one — the only report anybody could act on. The symptom is "it worked, then froze"; read the log.

It is client-side by construction — it is driven by the browser's frame clock — so a body that reaches a server function is refused at compile time. A round trip per frame is never what anyone meant.

Examples#

A create form, symmetric with its edit form — on mount seeds the draft; the inputs bind to it; Create commits:

entity Organization { string Name; string Slug; }

[Page("/org/new")]
[Render(CSR)]
component OrgCreatePage() {
  Organization draft;
  on mount { draft = new Organization {}; }

  action Create() { UnitOfWork.Commit(); Navigation.Close("/org/new", true); }

  render {
    Stack(gap: 4) {
      Input(value: draft.Name, placeholder: "Organization name");
      Input(value: draft.Slug, placeholder: "team-slug");
      Button("Create", onPress: Create);
    }
  }
}

Both bookends on one page — set a title at open, log the close at teardown:

[Page("/report")]
[Render(CSR)]
component Report() {
  string range = "";
  on mount { range = "last-30-days"; }     // prime a filter the moment the page opens
  on unmount { Log.Information("report closed"); } // a parting side-effect, after any child region tears down

  render { Text(range); }
}

See also#

  • component — the component these live in, and its other members (fields, action, server reads).
  • on change — the reactive sibling: a block that re-runs whenever a value it read changes, not just once.
  • The reactivity & lifecycle model — the whole execution model: declarations vs on mount vs on change vs on unmount.
  • creating & saving datanew Entity{ … } and UnitOfWork.Commit(), and the two-way Input(value: entity.Field) binding.
  • routes and pages — binding the page to a route (a create page is just another route).

Related

component

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

on change

`on change { … }` is a reactive **side-effect**: the runtime re-runs it whenever a value it read changes, so it's how…

The reactivity & lifecycle model

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

creating & saving data

A UI `action` creates, updates and deletes data by writing `new Entity { … }`, assigning fields, and calling…

routes and pages

How a component becomes a page: it declares a route with `[Page("/catalog/{slug}")]`, and navigating to a matching path…

Canvas

A drawing surface, and the verbs that paint on it. Put a `Canvas` in a render block, call `Draw.*` from an `on frame`…