Skip to content
BACK TO DOSSIER

// State management

quanta

A framework-agnostic reactive state library built on Proxy-based dependency tracking, split into a core, a React binding, and DevTools. Stable at 2.0.0.

Open source · v2.0.0 stable2 min read

The problem

State abstractions often hide the path between an update and the UI it changes. That hiding is sold as convenience, and it genuinely is — right up until something updates that you did not expect, and the framework offers no way to trace why.

QuantaJS keeps that path visible without making application code noisy. Those two goals pull against each other, and the tension between them is most of what the project is about.

The approach

Dependencies are discovered by execution rather than declared. A reactive effect sets itself as the active subscriber, runs, and every property it reads registers it — so the dependency set is always exactly what the code actually touched, including through branches.

code
import { createStore } from "@quantajs/core";
 
const cart = createStore("cart", {
  state: () => ({ items: [], coupon: null }),
  getters: {
    subtotal: (state) => state.items.reduce((sum, i) => sum + i.price, 0),
    total: (state) => applyCoupon(state.subtotal, state.coupon),
  },
  actions: {
    add(item) {
      this.items.push(item);
    },
  },
});

Getters are cached computed values that invalidate when a dependency moves. Actions are plain methods with this bound to the store. There is no reducer, no action type, and no dispatch — the traceability comes from the dependency graph being real and inspectable, not from ceremony at the call site.

The three packages

The library was split in 2025 so that the reactivity core carries no framework dependency:

  • @quantajs/corecreateStore, reactive, computed, watch, batching, and persistence adapters for localStorage, sessionStorage, and IndexedDB, with schema migrations
  • @quantajs/reactuseStore, useStoreSelector, useWatch, QuantaProvider, built on useSyncExternalStore so it is correct under concurrent rendering
  • @quantajs/devtools — a store inspector and action log that mounts into a shadow root, so it cannot inherit or leak application styles

A vanilla or Node consumer installs the core alone and pays nothing for React.

The hard part

Proxy-based reactivity assumes every meaningful read is a property access. For plain objects that holds. For Map and Set it is false — map.get("apple") reads the method, and the dependency belongs to the key, which never appears as a property at all.

Collections therefore get instrumented methods rather than trap-level tracking, and the operations that invalidate more than they write — clear(), and updating an existing key that iteration subscribers can see — have to notify a surface wider than the properties they touched. Getting that consistently right across get, has, size, and iteration is the substance of the 2.0.0 release.

I wrote up the whole dependency engine and the clear() bug in Reactivity is easy until someone calls .clear().

What it taught me

Designing a state library is mostly designing for the moment something goes wrong. The API for the happy path is easy. The one that makes a confusing re-render explainable at 2am is the one worth iterating on — and the invalidation surface of an operation is rarely the same as the properties it writes.

← ALL WORKCASE STUDY / QUANTA