Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

action vs. method

action name(params) { … } · RetType name(params) { … } / => expr;

A component's `action` and its plain `method` run the exact same body grammar with the exact same capabilities — reads, writes, commits, effects, binding to an event prop, and the same `.Pending` reactive flag. The one real difference is structural: an action is always void, a method may declare a return type — which is what lets a method (not an action) be called for its value from a render expression. Reach for `action` to name something a control binds to; reach for a `method` for anything that computes a value, including logic more than one action shares.

stable2 examples compiled by CIuiauthoringreactivity

Summary#

A component's action and its plain method are the SAME imperative-body mechanism wearing two headers. Both read and write the component's own state, both write entities and commit, both invoke effects, both bind to an event prop (onClick/onChange/onSubmit), and both expose a .Pending flag a control can read while an invocation of them is in flight. Nothing is reachable from an action that a method cannot also do. The one structural difference is that an action is always void — it cannot declare a return type — while a method may, and a return type is what lets a method be called for its value from a render expression (Calling helpers from render).

Signature#

action name(params) { … }             // always void — no return type may be written
RetType name(params) => expr;         // a plain method — may return a value
RetType name(params) { … }            // …or a block body; `void` is legal too

Description#

What's actually different — and what isn't#

actionmethod
Body grammarstatements, same as a methodstatements, or a single expression body
May read/write the component's own fieldsyesyes
May write entities and UnitOfWork.Commit()yesyes
May invoke effects (Log, Http, a server function, …)yesyes
Bindable to an event prop (onPress: Save)yesyes — the return value, if any, is simply discarded
Callable from another imperative body (on mount { Reload(); })yesyes
Reading .Pending on the memberyes (AL58)yes — identical mechanism (Pending)
Declares a return typeno — always voidyes
Callable for its value from a render expression, if purenot useful — has no value to giveyes (Calling helpers from render)

async belongs to neither: it is refused anywhere in Osy#, on a function, a class method, or a component member alike — effects run in place, so there is nothing for it to mean.

Everything in the first seven rows is the SAME mechanism, checked the SAME way, regardless of which keyword declared the member. The only row that is actually structural is "declares a return type" — and the render-call row follows from it, not from a separate rule about actions.

Nothing is only possible from an action#

A plain void method writes an entity, commits, and binds directly to an event prop — no action keyword anywhere:

```osy title="a plain method writes, commits, and binds to an event — no action needed" test app=ui-action entity Widget { [Required, MaxLength(80)] string Name; security { allow read, create when IsAuthenticated || IsAnonymous; } }

[Route("/widgets")] [AllowAnonymous] [Render(CSR)] component WidgetsPage() { live var widgets = Widget.ToList();

void AddOne() { var w = new Widget { Name = "from a method" }; UnitOfWork.Commit(); }

render { Stack { foreach (var w in widgets) { Text(w.Name); } Button("Add", onPress: AddOne); } } }


### `.Pending` reads the same on both   {#pending}
`<verb>.Pending` ([Pending](/reference/ui/pending/)) is not an `action`-only surface — it is typed off a real baseline class
(`Osysharp.ActionState`) that every declared VERB gets a read on, action or method alike:

```osy title="`.Pending` on a plain method — identical to an action's" test app=ui-action
[Route("/reload")]
[AllowAnonymous]
[Render(CSR)]
component ReloadRow() {
  live var widgets = Widget.ToList();

  int Reload() { return widgets.Count; }

  render {
    Stack {
      Text(Reload.Pending ? "Counting…" : "Idle");
      Button("Recount", onPress: Reload);
    }
  }
}

There is no .Error on a verb of either kind — ActionState carries exactly one field, Pending. save.Error is refused the same as any other unknown member; the unrelated .error that a server-read QUERY exposes (var x = Entity.ToList() → .items/.total/.loading/.error/.hasMore, see component) is a different surface at a different grain.

Only a method is USEFUL to call from render — and the check does not care which keyword you wrote#

Calling helpers from render lets a render expression call a pure client helper and render the value it returns. A method can be that helper because it may declare a real return type; an action cannot, because it is always void and has nothing to give render. This is a consequence of the return-type rule, not a second, action-shaped restriction — the purity check itself inspects what a body DOES, never which keyword declared it:

action Bump() { count = count + 1; }

render { Text(Bump()); }
// error: `Bump(…)` cannot be called from a render expression — it assigns the component's own state (`count`).
// A render expression must run synchronously and purely on the client; move the call into an `action`/`on mount`
// and render the value it produces.

A method with the identical body (int BumpValue() { count = count + 1; return count; }) is refused with the identical message, naming BumpValue instead of Bump — the check never asked which keyword declared the member.

When to reach for which#

  • Something a control binds to — onClick/onChange/onSubmit, or a lifecycle hook that just performs work — is an action. It reads as an event name, and it is the shape every existing example in this reference uses for a handler.
  • Something that computes and returns a value — a subtotal, a formatted label, a helper two actions both call and both need the result of — is a method. It is callable from render when pure, and its name reads as a question with an answer rather than as an event.
  • Logic several actions share that does not need the component's own state at all belongs in a class instead of either — see [[class-methods#method-or-function]] for the split, and store for the (currently limited) story on sharing state across components.

See also#

  • component — the member table this page's "difference" row expands on, and the receiver model both kinds share.
  • Pending — the full .Pending/Pending.Any reference.
  • Calling helpers from render — the purity rule that decides what may be called from render, and why only a method is useful there.
  • class methods — moving shared logic out of a component entirely.
  • store — sharing STATE and actions across components (what works today, and what does not yet).

Related

component

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

Pending

When a control's action waits on the server, the platform shows a busy spinner and disables the control — but only…

Calling helpers from render

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

class methods

Behaviour attached to a class — a method with a receiver, called as value.Method(). Classes are in-memory values, so a…

store

`store` declares a headless reactive unit — the same members a component has, minus `render` — meant to hold UI state…