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.
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.
