Skip to content
AbloAblo
Esc
navigateopen⌘Jpreview
On this page

v0.2.1

React bindings hardening. Fixes two infinite-loop classes that surfaced in downstream apps as React error #185 (“Maximum update depth exceeded”), and exposes sync-status reactivity as a first-class observable + hook.

Fixed

  • useQuery / useOne no longer loop on getSnapshot. The useSyncExternalStore adapter was returning a fresh view.results.slice() on every call, which React’s post-commit consistency check interpreted as “store updated mid-render” — scheduling another render, another snapshot, another mismatch, ad infinitum. The snapshot is now cached in a ref and only refreshed inside the subscribe callback right before onChange() fires. Affected every tree with multiple simultaneous useQuery subscribers.

Added

  • BaseSyncedStore sync status is now properly observable. syncStatus and dataReady are annotated observable; isReady, isSyncing, isOffline, isReconnecting, isError, hasUnsyncedChanges are computed. Before, these were plain getters over plain fields — reaction(() => store.isReady, ...) silently never fired. Existing observer / reaction call sites that relied on the implicit pool.size trigger will continue to work; new call sites should read these observables directly.
  • useSyncStatus() React hook. Returns { isReady, isSyncing, isOffline, isReconnecting, isError, hasUnsyncedChanges } as a reactive snapshot, bridged via useSyncExternalStore with a correctly-cached snapshot. Replaces hand-rolled reaction bridges in consumer providers. See docs/react.md.
  • SyncStoreContract surfaces the status getters so TypeScript autocomplete works from the useSyncContext() return value without a cast.

Documentation

  • llms.txt and docs/react.md gained a “Common pitfalls” section covering the three traps this release addresses: don’t wrap providers in observer(), getSnapshot must return a cached reference, and sync-status fields are real observables (don’t watch pool.size as a proxy).

Migration

No breaking changes. Optional: replace any local reaction(() => store.isReady, setReady, { fireImmediately: true }) bridges in your own providers with const { isReady } = useSyncStatus() for consumers below the store provider.

Was this page helpful?