# action vs. method

> 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.

<!-- id: ui-action · area: ui · stability: stable · html: https://osysharp.com/reference/ui/action/ -->

## Summary        {#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](https://osysharp.com/reference/ui/render-calls/)).

## Signature      {#signature}
```osy syntax
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    {#description}

### What's actually different — and what isn't   {#difference}
| | `action` | `method` |
|---|---|---|
| Body grammar | statements, same as a method | statements, or a single expression body |
| May read/write the component's own fields | yes | yes |
| May write entities and `UnitOfWork.Commit()` | yes | yes |
| May invoke effects (`Log`, `Http`, a server function, …) | yes | yes |
| Bindable to an event prop (`onPress: Save`) | yes | yes — the return value, if any, is simply discarded |
| Callable from another imperative body (`on mount { Reload(); }`) | yes | yes |
| Reading `.Pending` on the member | yes (AL58) | yes — identical mechanism ([Pending](https://osysharp.com/reference/ui/pending/)) |
| Declares a return type | **no** — always void | **yes** |
| Callable for its value from a `render` expression, if pure | not useful — has no value to give | **yes** ([Calling helpers from render](https://osysharp.com/reference/ui/render-calls/)) |

`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   {#capability}
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](https://osysharp.com/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](https://osysharp.com/reference/ui/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   {#render}
[Calling helpers from render](https://osysharp.com/reference/ui/render-calls/) 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:

```osy title="✗ writing state is illegal in render, whichever keyword declared it" syntax
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   {#choosing}
- **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](https://osysharp.com/reference/class/methods/) instead of either — see [[class-methods#method-or-function]] for the split, and
  [store](https://osysharp.com/reference/ui/store/) for the (currently limited) story on sharing state *across* components.

## See also       {#see-also}
- [component](https://osysharp.com/reference/ui/component/) — the member table this page's "difference" row expands on, and the receiver model both kinds
  share.
- [Pending](https://osysharp.com/reference/ui/pending/) — the full `.Pending`/`Pending.Any` reference.
- [Calling helpers from render](https://osysharp.com/reference/ui/render-calls/) — the purity rule that decides what may be called from render, and why only a `method` is
  useful there.
- [class methods](https://osysharp.com/reference/class/methods/) — moving shared logic out of a component entirely.
- [store](https://osysharp.com/reference/ui/store/) — sharing STATE and actions across components (what works today, and what does not yet).
