Skip to content

Watch Annotations

Explicitly mark properties for tracking (shallow comparison): @computed getters, and fields holding an observable.box(), .array(), .map() or .set() instead of being annotated with @observable.

class Model {
@watch field = observable.box(false);
constructor() {
makeObservable(this);
}
}

A watcher leaves a @computed out unless it is marked (see Computed properties). Here, the prices of the items count through the total, although changes to elements are not tracked on their own:

class Model {
@observable items = [{ price: 10 }];
constructor() {
makeObservable(this);
}
@watch
@computed
get total() {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
const model = new Model();
const watcher = Watcher.get(model);
runInAction(() => {
model.items[0].price = 20;
});
watcher.changedKeys // Set(["total"])

Track with identity comparison only

class Model {
@watch.ref @observable items = [1, 2, 3];
constructor() {
makeObservable(this);
}
}
const model = new Model();
const watcher = Watcher.get(model);
runInAction(() => {
model.items.push(4); // No change detected (same array reference)
});
watcher.changed // false

Exclude properties from tracking

class Model {
@unwatch @observable internalState = false;
@observable userField = false;
@computed
get derivedState() {
return this.internalState ? "active" : "inactive";
}
constructor() {
makeObservable(this);
}
}
const model = new Model();
const watcher = Watcher.get(model);
runInAction(() => {
model.internalState = true; // Not tracked
});
watcher.changed // false
watcher.changedKeys // Set() - derivedState is a @computed, so it is not tracked either

Use @nested to track changes in nested object properties. Read Nested section for detail.

  • Each nested object gets its own Watcher instance automatically
  • Nested watchers are independent - calling reset() on a child watcher doesn't affect the parent
class Parent {
@nested @observable child = new Child();
@nested @observable items = [new Item()];
constructor() {
makeObservable(this);
}
}
class Child {
@observable value = false;
constructor() {
makeObservable(this);
}
}
const parent = new Parent();
const watcher = Watcher.get(parent);
runInAction(() => {
parent.child.value = true;
parent.items[0].value = true;
});
watcher.changed // true
watcher.changedKeys // Set([]) - no direct property changes
watcher.changedKeyPaths // Set(["child.value", "items.0.value"]) - nested change tracked
// Nested watchers are independent
const childWatcher = Watcher.get(parent.child);
childWatcher.reset(); // Does NOT affect parent watcher
watcher.changed // still true