Skip to content

Framework Adapters

Thin bridges connecting StateLoom's Subscribable<T> contract to framework-specific reactivity systems. Each adapter is 50-200 lines of pure bridging code with zero business logic.

Available Adapters

PackageFrameworkBridging StrategySize
@stateloom/reactReact 18+useSyncExternalStore~1.1 KB
@stateloom/vueVue 3.2+shallowRef + effect()~1.5 KB
@stateloom/solidSolid.js 1+createSignal + dual subscription~0.2 KB
@stateloom/svelteSvelte 4/5Native store contract (Readable/Writable)~0.1 KB
@stateloom/angularAngular 17+Angular Signals + RxJS Observable~0.3 KB

The Universal Contract

Every signal, computed, store, and atom in StateLoom implements the Subscribable<T> interface:

typescript
interface Subscribable<T> {
  get(): T;
  subscribe(callback: (value: T) => void): () => void;
}

Each framework adapter bridges this single interface to the framework's native reactivity primitives. Adapters contain no state management logic -- they are purely mechanical bridges.

Feature Comparison

FeatureReactVueSolidSvelteAngular
Signal bridginguseSignal()useSignal()useSignal()toReadable()toAngularSignal()
Store with selectoruseStore()useStore()useStore()computed() + toReadable()injectStore()
Atom hooksuseAtom() / useAtomValue() / useSetAtom()via useSignal()via useSignal()via toReadable()via toAngularSignal()
Proxy integrationuseSnapshot()--------
Two-way binding------toWritable() + bind:value--
RxJS bridge--------toObservable() / fromObservable()
SSR scopeScopeProviderstateloomPlugin / ScopeProvider / provideScope()ScopeProvidersetScope() / getScope()provideStateloomScope() / injectScope()
Concurrent renderinguseSyncExternalStoreN/AN/AN/AN/A
Auto-cleanupReact lifecycleonScopeDisposeonCleanupSvelte store contractDestroyRef.onDestroy
Sub-path imports/store, /atom, /proxy--------

Paradigm Support by Adapter

ParadigmReactVueSolidSvelteAngular
Core signalsuseSignal(signal)useSignal(signal)useSignal(signal)toReadable(signal) / toWritable(signal)toAngularSignal(signal)
StoreuseStore(store, sel)useStore(store, sel)useStore(store, sel)toReadable(computed(() => store.getState().slice))injectStore(store, sel)
AtomuseAtom(atom)useSignal(atom)useSignal(atom)toReadable(atom)toAngularSignal(atom)
ProxyuseSnapshot(proxy)--------

TIP

Atoms implement Subscribable<T>, so they work with any adapter's signal bridge. Only React has dedicated atom hooks (useAtom, useAtomValue, useSetAtom) with the [value, setter] tuple pattern.

Adapter Bridging Strategies

React: useSyncExternalStore

React 18's concurrent rendering can "tear" -- reading different values during a single render pass. useSyncExternalStore is the only safe way to subscribe to external stores in concurrent mode. The React adapter also uses a StateLoom effect() inside subscribe to track computed signal dependencies through the reactive graph.

Vue: shallowRef + effect()

The Vue adapter creates a shallowRef and uses StateLoom's effect() to track dependencies. When the source changes, the effect updates the ref, and Vue's reactivity system picks up the change. shallowRef prevents Vue from deeply wrapping StateLoom-managed state with its own reactivity.

Solid: Dual Subscription

The Solid adapter uses both effect() (for graph-integrated sources like signals and computed) and subscribe() (for plain Subscribable objects). createSignal with equals: false ensures all updates propagate. The setValue(() => next) pattern safely handles function-typed values.

Svelte: Native Store Contract

Svelte's $store syntax works with any object implementing { subscribe }. The only gap: Svelte requires subscribe to call the callback immediately with the current value, while StateLoom's subscribe() only fires on changes. The toReadable/toWritable bridges close this gap.

Angular: Angular Signals + RxJS

Angular uses both RxJS Observables (for async pipes and operators) and Angular Signals (for template bindings). The adapter provides toObservable for the RxJS path, toAngularSignal for the Signals path, injectStore for store-to-template binding, and fromObservable for bridging RxJS back into StateLoom.

SSR Scope Integration

All adapters follow the same SSR pattern: provide a scope via the framework's context/injection system, read it in components that need scoped state.

FrameworkScope ProviderScope Consumer
React<ScopeProvider scope={scope}>useScopeContext()
Vueapp.use(stateloomPlugin, { scope }) / <ScopeProvider> / provideScope()useScope()
Solid<ScopeProvider scope={scope}>useScope()
SveltesetScope(scope) in parent componentgetScope()
AngularprovideStateloomScope(scope) in bootstrapApplicationinjectScope()

Choosing an Adapter

Use the adapter that matches your framework. If you use multiple frameworks in a monorepo, each app uses its own adapter while sharing the same @stateloom/core signals and stores.

If your framework is...Install
React 18+ (Vite, Next.js, Remix)@stateloom/react
Vue 3.2+ (Vite, Nuxt)@stateloom/vue
Solid.js 1+ (Vite, SolidStart)@stateloom/solid
Svelte 4/5 (Vite, SvelteKit)@stateloom/svelte
Angular 17+ (Angular CLI, Analog)@stateloom/angular
Vanilla JS/TS (no framework)No adapter needed -- use subscribe() directly

TIP

All adapters peer-depend on @stateloom/core and their respective framework. Paradigm packages (@stateloom/store, @stateloom/atom, @stateloom/proxy) are optional peer dependencies -- install only what you use.

Individual Adapter Documentation