Subscribing to an atom
So far, we have used read
to access atom values on demand.
To build a truly reactive app - where your UI automatically reflects global state changes - you will need a way to stay in sync.
That is where watch
comes in. It lets you subscribe to an atom and run a callback whenever the value of the atom changes.
Before we proceed...
In practice, you will rarely use watch
directly. Most apps rely on framework-specific bindings like useAtom
in React.
Still, it is useful to understand how atom subscriptions work under the hood, especially when debugging, writing side effects or building your own abstractions.
Using watch
The watch
function takes two arguments: the atom you want to observe, and a callback. The callback will run every time the atom's value changes.
import {atomAction, atomReducer, dispatch, read, watch} from 'reago'
function $counter() {
const [value, increase] = atomReducer(x => x + 1, 0);
atomAction(increase, []);
return value;
}
const watcher = watch($counter, () => {
console.log(`Counter changed to ${read($counter)}`);
});
dispatch($counter)(8); // prints Counter changed to 8
dispatch($counter)(42); // prints Counter changed to 42
The watch
call returns a watcher object representing the created observer. Make sure to call its .clear()
method when you are done observing the atom to avoid memory leaks.
watcher.clear();
Initial call behavior
By default, the callback is only triggered when the atom's value changes. If you want to run logic immediately with the current value, you can do a read
upfront.
onCounterChange();
const watcher = watch($counter, onCounterChange);
Watches are reactive
If your atom depends on other atoms, the watcher will respond to any change in the dependency graph.
For example:
import {atomAction, atomState, dispatch, read, watch} from 'reago';
function $number() {
const [value, setValue] = atomState(0);
atomAction(setValue, []);
return value;
}
function $doubledNumber() {
return read($number) * 2;
}
const watcher = watch($doubledNumber, () => {
console.log(`Doubled number changed to ${read($doubledNumber)}`);
});
dispatch($number)(21); // prints 42
Whenever $number
changes, $doubledNumber
is recomputed, and your callback will fire.
Mounted atoms
When you call watch
, the atom becomes mounted - meaning it is actively kept up to date.
All atoms track their dependencies, mounted or not. But mounted atoms differ in one key way: they recompute immediately when any of their dependencies change, instead of waiting for the next read
.
In other words, a mounted atom is live. It reacts to changes as they happen, keeping its value up to date so that subscribers like watch
or UI bindings get notified right away.
You can think of mounting an atom like mounting a UI component in the DOM. It goes from being just a declaration to being live.
Mounting is recursive: if a mounted atom depends on other atoms, those atoms will also be mounted.
Once there are no subscribers left - no explicit watchers and no mounted dependents - the atom is unmounted and goes back to recomputing lazily on demand.
In the next article, we are going to learn about side effects, including running effects when an atom is mounted or unmounted.