Summary#
Render is where you turn data into what's on screen — and sometimes the value you want to show needs a small
computation. Write it as a component method (or any pure client helper) and call it right in the render
expression:
component Cart() {
live var items = LineItem.ToList();
decimal Subtotal() => items.Sum(i => i.Price * i.Qty); // a pure helper over component state
render { Text(Subtotal()); } // call it in render — natural C#
}The call runs in the browser, synchronously, as part of rendering — and re-runs (repainting only that slot) when a value the helper read changes. See The reactivity & lifecycle model for the fine-grained update model.
Signature#
render { Text(Helper(arg)) } // Helper is a component method, a client class method, or a top-level functionA render call is legal iff it is client-side (never suspends) ∧ writes no state ∧ invokes no effect — the compiler checks all three.
Description#
What you can call#
- A component
method—int Twice() => n * 2;thenText(Twice()). It reads the component's own state throughthisimplicitly, exactly as anactiondoes. - A client class method —
money.Formatted()on aclassvalue whose method body is client-runnable. - A top-level function whose body is client-runnable — an ordinary
string Shout(string s) { … }. You mark nothing: the compiler decides where it can run from what it touches, and refuses the call if the answer is the server.
Pure stdlib calls (Convert.ToString, Enum.Label, …) have always been render-slot material; this extends the same
door to your own helpers, so you don't have to hoist a one-line computation into a live var just to name it.
What the compiler refuses — and why#
A render expression must run synchronously and purely on the client (it can re-run many times as data changes, and it may not block or cause side effects). So a call that breaks any of the three rules is a compile error that names the offending reach:
| The call… | Error |
|---|---|
| reaches a data read or a server function | … cannot be called from a render expression — it reaches server-side work … |
| writes the component's own state | … it assigns the component's own state (count) … |
invokes an effect (Log, Http, …) | … it invokes the effect Log.Information … |
The fix is always the same: move the imperative call into an action or on mount, store what it produces in state,
and render that. A data read is a live var query (component); a one-time load is on mount / on unmount.
Why this is safe by construction#
The fine-grained renderer re-runs a slot's expression inside a tracking effect whenever a value it read changes (The reactivity & lifecycle model). "A render expression is pure" therefore isn't a convention you must remember — it's a compile-time guarantee, which is what lets you call your helpers in the view without ever creating a render loop.
Examples#
A cart line renders a per-row subtotal via a pure helper — no live var needed for the one-liner:
entity Product { string Name; decimal Price; }
[Page("/catalog")]
[Render(CSR)]
component Catalog() {
live var products = Product.ToList();
decimal WithTax(decimal price) => price * 1.25m; // a pure client helper
render {
foreach (var p in products) {
Text(p.Name);
Text(WithTax(p.Price)); // called in render — the compiler proves it pure
}
}
}See also#
- component — the
methodmember, and thelive varquery you'd reach for when a value needs the server. - The reactivity & lifecycle model — the fine-grained model that makes "render is pure" a structural requirement.
- on change — the reactive block for pushing a value out of the component (the opposite direction).
- on mount / on unmount —
on mount, for one-time imperative setup a render call may not do.