Skip to content

Watching Changes

Reset the watcher to clear all tracked changes:

const watcher = Watcher.get(model);
runInAction(() => {
model.name = "John";
});
watcher.changed // true
watcher.changedKeys // Set(["name"])
// Clear all tracked changes
watcher.reset();
watcher.changed // false
watcher.changedKeys // Set()
watcher.changedTick // 0n

Mark the watcher as changed without incrementing the tick (useful for external state synchronization):

watcher.assumeChanged();
watcher.changed // true
watcher.changedTick // 0n (not incremented)

The changedTick property is a counter that increments with each tracked change.

class Model {
@observable name = "";
@observable age = 0;
constructor() {
makeObservable(this);
}
}
const model = new Model();
const watcher = Watcher.get(model);
watcher.changedTick // 0n
runInAction(() => {
model.name = "John";
model.age = 30;
});
// Each change increments the tick
watcher.changedTick // 2n (one for name, one for age)
watcher.changedKeys // Set(["name", "age"])
watcher.reset();
// After reset, both changedKeys and changedTick are cleared
watcher.changedKeys // Set()
watcher.changedTick // 0n

Most useful with reaction() to trigger side effects when changes occur:

const model = new Model();
const watcher = Watcher.get(model);
// React to any tracked changes
reaction(
() => watcher.changedTick,
() => {
// Process changes here...
saveToBackend(model);
}
);
runInAction(() => {
model.name = "John";
model.age = 30;
});
// Reaction triggers once after the transaction completes

Use unwatch() as a function to run code without changes being detected by Watcher.

const model = new Model();
const watcher = Watcher.get(model);
// Changes inside unwatch() are not tracked
unwatch(() => {
model.name = "John";
model.age = 30;
});
watcher.changed // false
// Normal changes are still tracked
runInAction(() => {
model.name = "Jane";
});
watcher.changed // true

Only what the function itself changes goes untracked. Watching resumes as soon as it returns, so the changes made around it are tracked as usual, even when all of it happens in one transaction.

runInAction(() => {
model.field1 = true; // Tracked: before unwatch begins
unwatch(() => {
model.field2 = true; // Not tracked
});
model.field3 = true; // Tracked: unwatch has returned
});
watcher.changedKeys // Set(["field1", "field3"])

⚠️ One limitation: a watcher processes the changes of a transaction when the outermost one ends, where all it can tell is when a key first changed in it. So a key the function changes stays untracked for the rest of that transaction:

runInAction(() => {
unwatch(() => {
model.field1 = true; // Not tracked
});
model.field1 = false; // ⚠️ NOT tracked: the same key, already changed inside unwatch
model.field2 = true; // Tracked: a different key
});
watcher.changedKeys // Set(["field2"])

The reverse order is fine: a key changed before the function and again inside it is tracked.