---
id: "packages/active-state"
title: "active-state"
description: "Framework-independent state for client logic with in-flight work - stores, derived values, effects, operations and lifetimes, with a React adapter."
url: "/packages/active-state"
locale: "en"
revision: "8bde0c2c11d281d4"
---

# active-state

`@workspace/active-state` keeps client state outside your components. The core has six nouns:
`Store`, `Derived`, `Effect`, `Operation`, `Scope` and the transaction (`transaction()`), plus
`batch` and `shallow`. A React adapter (`useStore`) and a testing entry sit on top. There is no
provider: a model is plain objects you create where you need them.

**Example: A store, derived values and useStore**

Client: Runs entirely in your browser. Source: `examples/state/cart-total.tsx`.

```tsx
// apps/docs/examples/state/cart-total.tsx
'use client';

import {Derived, shallow, Store, useStore} from '@workspace/active-state/react';
import {Button} from '@workspace/active-ui/components/primitives/button';

interface Line {
  product: string;
  price: number;
}

// Module scope: the model lives outside React and needs no provider.
const lines = new Store<Line[]>([{product: 'Notebook', price: 4.5}], {
  equal: shallow,
});
const total = new Derived({
  inputs: [lines],
  compute: (list) => list.reduce((sum, line) => sum + line.price, 0),
});
const count = new Derived({inputs: [lines], compute: (list) => list.length});

const PRODUCTS: Line[] = [
  {product: 'Pen', price: 1.25},
  {product: 'Folder', price: 2},
  {product: 'Stapler', price: 7.5},
];

/** A Store, two Derived values over it, and useStore in React. */
export default function CartTotal() {
  const items = useStore(lines);
  const sum = useStore(total);
  const n = useStore(count);
  return (
    <div className="flex flex-col gap-4">
      <div className="flex flex-wrap gap-2">
        {PRODUCTS.map((line) => (
          <Button
            key={line.product}
            variant="outline"
            size="sm"
            onClick={() => lines.update((list) => [...list, line])}
          >
            Add {line.product}
          </Button>
        ))}
        <Button
          variant="ghost"
          size="sm"
          onClick={() => lines.setState([])}
          disabled={n === 0}
        >
          Empty
        </Button>
      </div>
      <ul className="text-muted-foreground text-sm">
        {items.map((line, index) => (
          <li key={index}>
            {line.product}: {line.price.toFixed(2)}
          </li>
        ))}
      </ul>
      <p className="text-sm" aria-live="polite" data-cart-total>
        {n} item{n === 1 ? '' : 's'}, total {sum.toFixed(2)}
      </p>
    </div>
  );
}
```

## What it gives you

- **Simple state stays simple.** A `Store` is a value with `setState`, `update` and `subscribe`.
  You need none of the other nouns to use it.
- **Derived values with explicit inputs.** A `Derived` declares its inputs and computes from their
  values. A read is always current, observed or not.
- **Settle, then publish.** Subscribers receive `(value, previous)` after the whole affected graph
  has settled, once per round, so nobody sees a new input next to a stale derived value.
- **Two boundaries.** `batch` groups notifications and never reverts anything; `transaction` is
  synchronous and all-or-nothing, and a reverted transaction notifies nobody.
- **Owned async work.** An `Operation` has a policy (`latest-wins`, `drop-while-running`,
  `queue`), typed outcomes that never throw, and cancellation. It changes state only through
  `ctx.commit`, so a superseded or cancelled call cannot overwrite newer state.
- **Explicit lifetime.** A `Scope` owns stores, effects, operations and child scopes and disposes
  them in a fixed order.
- **React as an adapter.** `useStore` is `useSyncExternalStore` with a selection and `shallow`
  comparison by default.
- **Testable without React.** `@workspace/active-state/testing` captures errors, records events
  and drives operations step by step.

## What it does not do

- **No default equality on a store.** A `Store` without `equal` publishes every write, also an
  identical value. Pass `equal: shallow` (or your own) where you rely on value semantics.
- **No persistence, no network, no serialization.** Fetching, storage and hydration data are
  yours; an `Operation` only owns the life of the work.
- **No React context.** `useStore` reads the object you pass; it never reads a provider. An error
  in a subscriber or effect goes to the runtime's error sink (`reportError`), not to a React error
  boundary.

## Entry points

| Import | Contents |
| --- | --- |
| `@workspace/active-state` | the framework-independent core; imports no React |
| `@workspace/active-state/react` | the whole core again (the same objects), plus `useStore` and `useScoped` |
| `@workspace/active-state/testing` | test seams and the instrumentation protocol; internal and unstable, for tests |

Pick one of the first two per file: `/react` repeats the whole core.

## What your application provides

- **React 19** and `@types/react`, only where you import `/react`. The core needs nothing.
- **A TypeScript-aware build**: the package ships its TypeScript source.
- **The data and the side effects**: requests, storage, timers. Put them in an `Operation` or an
  `Effect` so their lifetime is owned.

## Dependencies

None at run time. `react` is an optional peer, needed only for `/react`.

## Next

- [Getting started](/packages/active-state/getting-started): receive the package and build a first
  model.
- [Async work](/packages/active-state/async-work): requests that cannot overwrite newer state.
