Skip to main content

Subscriptions & Action Hooks

Two independent ways to observe a store from the outside, without modifying the store itself — useful for logging, persistence, analytics, or building the kind of tooling a future DevTools extension would need.

$subscribe() — state changes​

Fires whenever the store's state changes, however that change happened (direct mutation, an action, or $patch()):

const unsubscribe = counter.$subscribe((mutation, state) => {
console.log(mutation.type, state); // 'direct' | 'patch object' | 'patch function'
});

// later
unsubscribe();

A $patch() call — object form or function form — fires exactly one $subscribe() notification, tagged 'patch object'/'patch function', no matter how many properties it touched. A direct mutation (counter.count++) fires one notification per change, tagged 'direct'.

Pass { detached: true } if the subscription should outlive whatever reactive scope it was created in (rarely needed — Nekuta's own internals use this for the React hooks' subscriptions, since a React render isn't itself a reactive scope).

$onAction() — action lifecycle​

Fires when an action is called, before it runs, with hooks to observe how it finishes:

const unsubscribe = counter.$onAction(({ name, args, after, onError }) => {
console.log('calling', name, 'with', args);

after((result) => {
console.log(name, 'finished with', result);
});

onError((error) => {
console.error(name, 'threw', error);
});
});

after() fires once — after the action's return value resolves, if it returned a Promise, or immediately for a synchronous action. onError() fires if the action throws (synchronously) or its returned Promise rejects; the error still propagates to the original caller either way — $onAction() observes, it doesn't swallow.

Both together​

A typical use is combining them to build an activity log — $onAction() for "what was called," $subscribe() for "what actually changed":

counter.$onAction(({ name }) => log(`${name}() called`));
counter.$subscribe((mutation) => log(`state changed (${mutation.type})`));

See the playground app's ActivityLog component for a complete, running example of this pattern.