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

Reference / UI

Naming a value in render

var lapsed = …; · bool lapsed = …; — name a value inside a render block, inferred or typed

A `render` block can name a value the way any C# block does — `var lapsed = …;` to infer the type, or `bool lapsed = …;` to declare it. The name is in scope for the rest of the block, so a value read twice is written once. A declared type pins the binding and is checked exactly as it is in a method body.

stable1 example compiled by CIuiauthoring

Summary#

Inside render { … } a value can be given a name:

var lapsed = DateTime.UtcNow > deadline;   // inferred from the initializer
bool lapsed = DateTime.UtcNow > deadline;  // declared, and checked

Both are the same binding. The name is visible to the following siblings in the block — not to anything before it, and not outside it — which is the scope a C# local has.

Signature#

var  <name> = <expression>;    // the type is inferred from the initializer
<Type> <name> = <expression>;  // the type is declared, and the initializer is checked against it

Description#

Why name a value at all#

A render expression is read where it is written, so a value used twice is otherwise written twice. Naming it once is shorter to read and impossible to get subtly different in the second copy.

Inferred or declared#

var takes the initializer's type. A declared type pins the binding, which is not always the same thing:

var half = 1;        // int    — `half / 2` is 0
decimal half = 1;    // decimal — `half / 2` is 0.5

The declared type is checked with the same rule a method body uses: the initializer must be assignable to it. A derived value goes into a base-typed binding, a literal takes the C# constant conversion, and there is no implicit conversion between decimal and double in either direction. An initializer that does not fit is a compile error naming both types.

A binding must be initialized where it is declared — there is no definite-assignment analysis, so bool flag; on its own is refused.

What a binding cannot do#

A binding names a value; it does not introduce a data read. Its initializer is held to the same client-runnable rule as every other render expression, so it cannot reach the server, write state, or run an effect. A value that needs a query belongs in a live var component field.

Examples#

A status label and a flag, each computed once and read several times:

[Page("/requests")]
[Render(CSR)]
[AllowAnonymous]
component Requests() {
  int[] ages = [1, 2, 5];

  render {
    Stack {
      // Declared, because the type is the point of the value.
      string heading = "Requests";
      Text(heading);

      foreach (var age in ages) {
        // Read twice below — written once here.
        bool lapsed = age > 3;
        Text($"{age}d: {(lapsed ? "lapsed" : "open")}{(lapsed ? " (closed)" : "")}");
      }
    }
  }
}

See also#

Related

component

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

Calling helpers from render

A render expression may call a PURE client helper — a component `method`, a client class method, a top-level function —…

The reactivity & lifecycle model

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