---
title: watch
description: Runs a handler while the scene compiles, each time a value the actor reads changes.
---

`watch(source, handler)` keeps a rule next to the state it depends on. `source` reads some of
the actor's values and returns one result. Whenever that result changes, the handler runs
with the new and the previous result, and anything it writes is recorded at that frame like
any other write in the scene.

```ts twoslash title="search.ts"
import { LayoutActor, signal, watch } from "@motionactor/core";

export class Search extends LayoutActor {
  query = signal("");
  open = signal(false);

  sync = watch(
    () => this.query().length > 0,
    (hasQuery) => this.open.set(hasQuery),
  );
}
```

```ts twoslash
// @filename: search.ts
import { LayoutActor, signal, watch } from "@motionactor/core";
export class Search extends LayoutActor {
  query = signal("");
  open = signal(false);
  sync = watch(
    () => this.query().length > 0,
    (hasQuery) => this.open.set(hasQuery),
  );
}
// @filename: search.scene.ts
// ---cut---
import { createScene } from "@motionactor/core";
import { Search } from "./search";

const compiled = createScene(function* (ctx) {
  const search = ctx.spawn(Search);
  yield* ctx.wait(0.5);
  search.query.set("ca");
  yield* ctx.wait(0.5);
  search.query.set("cat");
  yield* ctx.wait(0.5);
  return { search };
}).build({ viewport: { width: 1920, height: 1080 }, fps: 30 });

compiled.at(0).scene.search?.open; // false
compiled.at(15).scene.search?.open; // true
```

The scene only writes `query`, and `open` follows. Typing the third letter changes the query
but not the source's result, which was already `true`, so the handler does not run again.

## When it runs

A watch is registered when its actor spawns. The source runs then, and its result is the
starting point; the handler does not run for it. Plain objects and arrays are compared by
their contents, so a source that returns `{ ...this.bag.peek() }` changes only when the data
does. Anything else is compared with `Object.is`.

The compiler checks the source when a signal it read is written, and on every frame where a
value it read can change: each frame of a tween, and the frame of a later write. A watch on
`level() > 50` while `level` tweens from 0 to 100 runs on the frame `level` passes 50. A
source that reads `runtime.frame()` or `runtime.timeSeconds()` is checked on every frame.

A handler's writes are checked too, so one watch can set off another. The chain has to
settle; after 100 rounds at one frame, compiling throws.

## A handler that takes time

A generator handler animates. JavaScript has no generator arrows, so give the function a
`this` type:

```ts twoslash
import { createScene, LayoutActor, signal, watch } from "@motionactor/core";

class Badge extends LayoutActor {
  active = signal(false);

  fade = watch(
    () => this.active(),
    function* (this: Badge, active) {
      yield* this.appearance.opacity.tween(active ? 1 : 0.2, 1);
    },
  );
}

const compiled = createScene(function* (ctx) {
  const badge = ctx.spawn(Badge, { appearance: { opacity: 0 } });
  badge.active.set(true);
  yield* ctx.wait(0.5);
  badge.active.set(false);
  yield* ctx.wait(1.5);
  return { badge };
}).build({ viewport: { width: 1920, height: 1080 }, fps: 30 });

compiled.at(15).scene.badge?.appearance.opacity; // 0.5
compiled.at(30).scene.badge?.appearance.opacity; // 0.35
```

The handler runs on a branch of its own from the frame of the change, and the script does not
wait for it. When the result changes again before the branch finishes, the running branch
stops and a new one starts from the current value. Here the fade in is halfway at 0.5 when
`active` turns false, and the fade out starts from there. A handler still running when the
script ends keeps the scene going until it finishes.

A handler started by a write steps when the branch that wrote pauses, at the same frame. One
started by a frame of a tween steps before every other branch at that frame, so the script reads what it
wrote there.

## What it cannot read

The source runs while the scene compiles, so it can read only what the scene writes. A source
that reads a [channel](/docs/reactivity/channel), or a `derived` that reads one, throws, and so
does `runtime.viewport()`: neither has a value until a host resolves a frame. Compute from
them in a [`derived`](/docs/reactivity/derived).

`runtime.frame()` throws inside a handler. Use `this.cursor` for the frame or
`this.cursorSeconds` for the time in seconds.

To act on something another actor does, subscribe to its
[events](/docs/reactivity/events-and-subscribe).
