# on settled — run something once, when a stream finishes

> `on settled(x) { … }` runs once when the stream `x` finishes arriving — whether it completed or failed. It is how you store a streamed answer, and it runs exactly once without you having to guard it.

<!-- id: ui-on-settled · area: ui · stability: preview · html: https://osysharp.com/reference/ui/on-settled/ -->

## Summary        {#summary}
A `stream<T>` arrives in pieces and then stops. `on settled(x) { … }` is the block that runs when it stops — once,
whichever way it ended. That is where you save what arrived.

## Signature      {#signature}
```osy syntax
on settled(<a live var holding a stream>) { … }
```

## Description    {#description}
A component that watches a stream usually has something to do when it finishes: store the answer, mark the
conversation read, move on to the next step. `on settled` is that block.

**It runs exactly once, and you do not write a guard for it.** It is attached to the moment the stream *stops being
open*, which happens once and cannot happen twice. This matters more than it sounds, because the obvious alternative
does not work:

```osy title="✗ on change re-fires on every later render" syntax
on change { if (answer.Done) { Save(chatId, string.Concat(answer)); } }   // ✗ runs again and again
```

`on change` is a reaction — it re-runs whenever a value it read changes. `answer.Done` stays true once the stream is
done, so the block fires again on every later render. In a real app this stored one reply fourteen times.

**It runs when the stream FAILS too**, not only when it completes. A reply that broke off halfway still said what it
said, and the pieces that arrived are still there — so a block that saves the result gets to save the partial. If you
need to tell the two apart, ask the stream:

```osy title="settled runs on failure too — ask which it was" syntax
on settled(answer) {
  if (!answer.Failed) { Save(chatId, string.Concat(answer)); }
}
```

**It may write the component's own state**, which an `on change` block may not. A block that runs once cannot loop,
so there is nothing to protect against — the same reason `on mount` may.

**Name the stream.** A component can watch more than one, so `on settled` always says which: `on settled(answer)`.
Naming something that is not a stream is a compile error, and so is giving one stream two settle hooks — both would
run and the second one's writes would win, so put everything in one block.

**If nobody is watching, it does not run.** The hook belongs to the component, so a reader who navigates away before
the answer finishes never triggers it. Anything that must happen regardless belongs on the server, in the function
that produces the stream.

## Examples       {#examples}

Store a model's reply when it finishes — the whole reason the block exists:

```osy test app=chat-settle
entity Reply { string? Body; }

stream<string> Answer() {
  yield return "the wire ";
  yield return "is live";
}

void Store(string text) {
  new Reply { Body = text };
  UnitOfWork.Commit();
}

component Chat() {
  live var answer = Answer();
  on settled(answer) { Store(string.Concat(answer)); }

  render {
    Stack { Markdown(string.Concat(answer), streaming: !answer.Done); }
  }
}
```

## See also       {#see-also}
- [LlmClient.Stream — a model's answer as it is written](https://osysharp.com/reference/function/llm-stream/) — producing the stream this block waits on
- [on change](https://osysharp.com/reference/ui/on-change/) — for a value that keeps changing, rather than a one-time finish
