In components

Four hooks, each subscribing to exactly the path it names - so a hundred-row list does not re-render because an unrelated counter moved.

const [name, setName] = useStore<string>('$/agent/name', 'billing-worker');
const services = useStoreValue<Service[]>('$/services/list', []);
const metrics = useStoreSubtree('$/metrics');
const alerts = useCollection<Alert>('$/alerts/list');

useStore is state; useStoreValue is a view

They look similar and their second arguments mean different things, so the distinction is worth stating plainly:

  second argument writes? other readers see it
useStore(path, initial) an initial value yes, once, if the path is empty yes
useStoreValue(path, fallback) what this reader shows while empty never no

useStore is named after useState and behaves like it: the initial value is written the first time a component asks for a path that nothing has filled in, so every other reader of that path agrees with it immediately. Use it where a component owns a piece of state that happens to live in the store.

useStoreValue is for reading something another part of the application owns. Its fallback is a display default - 0 for a count nobody has computed yet - and it stays local to that one component. If two components pass different fallbacks for the same empty path, they show different things, which is exactly what “this is not shared” means.

When state is owned by nothing in particular - defaults for a screen, seeds for a demo - write it at boot or declare it with registerSchema({ initial }), which is clearer than making some component’s first render responsible for it.


Back to top

MIT licensed. Pre-1.0 - the surface is still moving.