Summary#
on every (<TimeSpan>) { … } is a component cadence hook: the runtime runs the block over and over, on the
interval you name, for as long as the component is mounted. It belongs to the on <event> family alongside
on mount / on unmount's on mount / on unmount and the reactive on change.
It is an auto-invoked action, so it can do everything an action can — assign fields, create rows with
new Entity{ … }, and call server functions. That last one is most of the point: "refresh this from the server
every thirty seconds" is the canonical cadence.
component Dashboard() {
int seconds = 0;
on every (TimeSpan.FromSeconds(1)) { seconds = seconds + 1; }
render { Text($"up for {seconds}s"); }
}Signature#
on every (<TimeSpan>) { <statements> } // the common form
on every (<TimeSpan>) (int elapsed) { <statements> } // …plus how many intervals passed since the last runThe interval is a TimeSpan, never a bare number — TimeSpan.FromSeconds(5),
TimeSpan.FromMilliseconds(250), TimeSpan.FromMinutes(1). A plain 500 is refused, because nothing in the source
would say whether it meant milliseconds or seconds and the two are a thousand-fold apart.
The hook is anonymous, and — uniquely — a component may declare more than one.
Description#
The runtime arms a timer for the interval you named. When it fires it runs the block, and only once the block has returned does it re-read the interval and arm the next one — so a body slower than its own cadence runs back-to-back instead of stacking copies of itself, and a body that awaits a server call holds the clock while it waits.
Because it is driven by the wall clock rather than the display, a hidden tab does not stop it: the browser may
throttle the timer, but time still passes and the missed intervals are reported to the next run (see
missed ticks are coalesced). That is the property that makes it right for a poll and wrong for an animation, and the
reverse of on frame — see on every or on frame?.
Several clocks in one component#
Each on every block is its own independent clock. This is the ordinary case, not an exotic one:
component Monitor() {
int frames = 0;
int fps = 0;
live var rows = Reading.ToList();
on frame (double dt) { frames = frames + 1; }
on every (TimeSpan.FromMilliseconds(500)) { fps = frames * 2; frames = 0; } // a display counter
on every (TimeSpan.FromSeconds(30)) { rows.Refresh(); } // a server poll
render { Text($"{fps} fps"); }
}A clock's identity is where it is declared, not the interval it happens to hold — so two blocks that name the same interval are still two clocks, and changing an interval retimes the existing clock rather than creating a new one.
Here is a whole page that compiles — one counter on a one-second clock, a second clock at a different cadence, and a
on mount beside them, so the three hooks are seen coexisting rather than described:
[Page("/uptime")]
[Render(CSR)]
component UptimePage() {
int seconds = 0;
int minutes = 0;
string label = "";
on mount { label = "counting"; }
on every (TimeSpan.FromSeconds(1)) { seconds = seconds + 1; }
on every (TimeSpan.FromMinutes(1)) { minutes = minutes + 1; }
render {
Stack(gap: 2) {
Text($"{label}: {seconds}s");
Text($"{minutes} minute(s)");
}
}
}Can the interval depend on state?#
The interval is an ordinary expression, re-read on each tick, so a cadence can depend on state:
component Game() {
int level = 1;
// Every ten levels takes a slice off the interval — the piece falls faster as you play.
on every (TimeSpan.FromSeconds(0.75 - (level - 1) * 0.06)) { StepDown(); }
}A change takes effect at the next tick: the interval already in flight runs to completion, then the clock re-reads and re-arms. Nothing cancels a pending tick early.
What happens to a tick that was missed?#
A page that could not keep up — a stalled tab, a body slower than its own interval — does not accumulate a queue of runs. The missed intervals are coalesced into the next run, which is told how many there were:
on every (TimeSpan.FromSeconds(1)) (int elapsed) {
clock = clock + elapsed; // count the time that really passed, not the ticks that really fired
}elapsed is 1 on an ordinary tick and higher only when the page fell behind. This is why there is no "what happens
on overrun?" setting: ignoring elapsed skips the backlog, and looping over it catches up — the choice is a line
of your code rather than a mode. It is the same shape Schedule uses for a missed server job, which records one
occurrence carrying the number it absorbed.
The next tick is armed only after the body returns — including a body that awaited a server call — so a slow body runs back-to-back rather than stacking copies of itself.
on every or on frame?#
Both repeat; they are for different things and neither substitutes for the other.
on frame (double dt) | on every (TimeSpan) | |
|---|---|---|
| driven by | the display (~60 times a second) | the wall clock, on your interval |
| hidden tab | pauses — the display is not drawing | keeps running (the browser may throttle it) |
| can reach the server | no | yes |
| how many per component | one | as many as you like |
| for | animation, physics, anything per-frame | polls, counters, "again in N seconds" |
Choose on frame when the answer to "how often?" is "every time the screen updates". Choose on every for
everything else — a five-minute poll on the frame clock would wake sixty times a second to do nothing 17,999 times out
of 18,000, and a poll on the frame clock silently stops the moment the reader switches tabs.
Examples#
The canonical cadence — refresh from the server on an interval, and a second clock at a different rate on the same
component, which is the part that has no equivalent in on frame:
entity Reading { int Value; }
[AllowAnonymous]
int Latest() { return Reading.OrderByDescending(r => r.Value).Select(r => r.Value).FirstOrDefault(); }
[Page("/dashboard")]
[AllowAnonymous]
component Dashboard() {
int latest = 0;
int seconds = 0;
// Reaches the SERVER — the whole point of the wall-clock hook.
on every (TimeSpan.FromSeconds(30)) { latest = Latest(); }
// A second clock, its own cadence. `elapsed` is 1 on an ordinary tick and higher only if the page fell behind,
// so counting it rather than counting ticks keeps the number honest across a stalled tab.
on every (TimeSpan.FromSeconds(1)) (int elapsed) { seconds = seconds + elapsed; }
render {
Stack(gap: 2) {
Text($"latest {latest}");
Text($"up {seconds}s");
}
}
}Limits — the clock runs on the client#
- The hook runs on the client. A cadence that must survive the page being closed is a server concern — declare a
Scheduleinstead, which is durable and operator-editable. - A body that throws stops that clock and logs the error. Only that one: a broken counter does not take the poll beside it down.
- The clock is disposed with the component, so navigating away stops it.
See also#
- on mount / on unmount —
on mount,on unmountand the per-frameon frame - on change — the reactive sibling: run a block when a value changes, rather than when time passes
- The reactivity & lifecycle model — how a field a cadence writes reaches the screen