observe

Runs a function now and again whenever a reactive value it read changes.

observe(fn: () => unknown): () => void
observe(() => {
  localStorage.setItem("draft", todo.draft);
});

observe(() => {
  const id = setInterval(refresh, props.interval);
  return () => clearInterval(id);
});

Parameters

Parameter Type Meaning
fn () => unknown Runs immediately, then after any value it read changes. If it returns a function, that function runs before the next run and when the observer stops

Returns a stop() function.

Behaviour

Topic Behaviour
Dependencies Collected fresh on every run: only what the last run read is watched
Batching Re-runs are queued and run once per microtask, after all synchronous writes
Lifetime In a component script, stops automatically when the element is removed. The exported observe from @realmorg/realm is not tied to any element
refs An observer at the top of a script sees refs once the markup exists, because refs is reactive
Errors A throwing observer is reported with console.error. Other observers keep running
Loops If observers keep re-triggering each other for 100 rounds in one flush, the queue is cleared and Update loop detected is reported
Reading without subscribing Wrap reads in untrack, imported from @realmorg/realm (it is not one of the script helpers)

Example

Previewindex.htmltitle-mirror.html
Observer runs:
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>observe demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="./title-mirror.html"></import-element>

  <title-mirror></title-mirror>
</body>
</html>
<custom-element name="title-mirror">
  <script>
    const draft = useState({ text: "REALM" });
    const log = useState({ runs: 0 });
    let runs = 0;

    observe(() => {
      refs.preview?.style.setProperty("letter-spacing", `${draft.text.length / 20}em`);
      log.runs = ++runs;
    });
  </script>

  <style>
    :host {
      display: grid;
      gap: calc(var(--spacing) * 2);
      width: min(calc(var(--spacing) * 100), 100%);
    }
  </style>

  <input aria-label="Text" _value="#draft.text" oninput="draft.text = this.value">
  <strong ref="preview"><slot name="#draft.text"></slot></strong>
  <small>Observer runs: <slot name="#log.runs"></slot></small>
</custom-element>

Calling other components from an observer

An observer subscribes to everything it reads, including state read inside another component's methods. Wrap such calls in untrack so the observer runs only when its own inputs change:

observe(() => {
  if (order.status === "error") untrack(() => refs.dialog.showModal());
});

Edit this page on GitHub · View as Markdown