Guide/Reactivity
Reactivity
The core ships no reactive primitive. QubeeStore exposes two methods, and every framework binds
to them differently:
store.getSnapshot(); // the current state — stable identity between writesstore.subscribe(fn); // returns an unsubscribe functionThat is deliberately React’s
useSyncExternalStore contract, which
also happens to be the smallest sensible one. Both are bound to their store, so each can be passed
on its own — exactly what useSyncExternalStore does with them.
import { useSyncExternalStore } from 'react';
function usePage(store: QubeeStore) { return useSyncExternalStore(store.subscribe, store.getSnapshot);}getSnapshot() returns the same object until the next write, which is what stops
useSyncExternalStore re-rendering forever.
Angular
Section titled “Angular”private readonly _state = signal(this.store.getSnapshot());
constructor() { this.store.subscribe(() => this._state.set(this.store.getSnapshot()));}
readonly currentPage = computed(() => this._state().page);const state = shallowRef(store.getSnapshot());store.subscribe(() => (state.value = store.getSnapshot()));No framework
Section titled “No framework”const stop = store.subscribe(() => console.log(qb.generateUri()));stop();Snapshots are frozen
Section titled “Snapshots are frozen”The returned state is deeply frozen. Mutating it throws in strict mode rather than silently corrupting a later read:
store.getSnapshot().includes.push('author');// TypeError: Cannot add property 0, object is not extensibleTwo guarantees have to hold at once — a stable identity between writes, and immunity from caller mutation. Freezing is what makes both true.
