Quick Start

This guide gets you from install to a working app with @quantajs/core, @quantajs/react, and optional @quantajs/devtools.

Coming from 2.0.0? See Migrating to 2.1.

1. Define a store

defineStore returns a definition — a blueprint you call to get the instance. It holds no state itself, which is what makes it safe at module scope even on a server.

// stores/counter.ts
import { defineStore } from '@quantajs/core';

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    doubled: (s) => s.count * 2,
  },
  actions: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    },
  },
});

Everything is inferred — you never restate a generic:

const counter = useCounterStore();

counter.count;       // number
counter.doubled;     // number
counter.increment(); // typed

2. Use it in React

import { useQuanta } from '@quantajs/react';
import { useCounterStore } from './stores/counter';

function Counter() {
  const counter = useQuanta(useCounterStore);

  return (
    <div>
      <p>Count: {counter.count}</p>
      <p>Doubled: {counter.doubled}</p>
      <button onClick={() => counter.increment()}>+</button>
      <button onClick={() => counter.decrement()}>-</button>
    </div>
  );
}

export default function App() {
  return <Counter />;
}

No provider is required in a client-only app — stores resolve against an ambient container. Add one when you need to control the container's lifetime, which is mostly under SSR:

import { QuantaProvider } from '@quantajs/react';

<QuantaProvider>
  <Counter />
</QuantaProvider>

3. Subscribe to less

useQuanta subscribes to the whole store. In a view that renders a lot, use a selector instead — it subscribes to exactly what the selector reads, so a component reading s.count is not woken by a write to s.name.

import { useQuantaValue, useQuantaActions } from '@quantajs/react';

/** Re-renders only when `count` changes. */
function CountLabel() {
  const count = useQuantaValue(useCounterStore, (s) => s.count);
  return <p>{count}</p>;
}

/** Never re-renders — it calls an action but reads no state. */
function IncrementButton() {
  const counter = useQuantaActions(useCounterStore);
  return <button onClick={() => counter.increment()}>+</button>;
}

4. Async, without the boilerplate

Every action carries pending, error and abort(). You do not write loading flags by hand.

export const useUsersStore = defineStore('users', {
  state: () => ({ list: [] as User[] }),
  actions: {
    async load() {
      const res = await fetch('/api/users', { signal: this.$signal });
      this.list = await res.json();
    },
  },
});
function Users() {
  const users = useQuanta(useUsersStore);

  return (
    <>
      <button onClick={() => users.load().catch(() => {})} disabled={users.load.pending}>
        {users.load.pending ? 'Loading…' : 'Load users'}
      </button>
      {users.load.error && <p role="alert">{users.load.error.message}</p>}
      <ul>{users.list.map((u) => <li key={u.id}>{u.name}</li>)}</ul>
    </>
  );
}

See Async Actions.

5. Persist state (optional)

import { defineStore, LocalStorageAdapter } from '@quantajs/core';

export const useSettingsStore = defineStore('settings', {
  state: () => ({ theme: 'light', language: 'en' }),
  actions: {
    toggleTheme() {
      this.theme = this.theme === 'light' ? 'dark' : 'light';
    },
  },
  persist: {
    adapter: new LocalStorageAdapter('settings'),
    debounceMs: 300,
  },
});

// Wait for restored state if you need it before rendering.
await useSettingsStore().$hydrated;

See Persistence.

6. DevTools (optional)

DevTools is opt-in as of 2.1 — it never attaches to window until you ask it to.

import { enableDevTools } from '@quantajs/core';

if (import.meta.env.DEV) {
  enableDevTools();
}
import { mountDevTools } from '@quantajs/devtools';

mountDevTools();

For React apps you can render <QuantaDevTools /> from @quantajs/react/devtools instead of calling mountDevTools yourself.

Without React

@quantajs/core has no framework dependency. The same store works from plain JavaScript:

const counter = useCounterStore();

const unsubscribe = counter.subscribe(() => {
  document.querySelector('#count')!.textContent = String(counter.count);
});

counter.increment();

What to Learn Next