---
id: "packages/active-state/examples"
title: "Examples"
description: "Every working example of active-state on one page. Each one shows the file that renders it."
url: "/packages/active-state/examples"
locale: "en"
revision: "3d6986a2d51e267d"
---

# Examples

All examples are **Client** examples: the model lives in your browser. The search example uses a
timer in place of a request, so an earlier query answers later than a newer one.

**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>
  );
}
```

**Example: A search that a late answer cannot overwrite**

Client: Runs entirely in your browser. Source: `examples/state/search-operation.tsx`.

```tsx
// apps/docs/examples/state/search-operation.tsx
'use client';

import {useState} from 'react';

import {
  Operation,
  Scope,
  Store,
  useStore,
  type Outcome,
} from '@workspace/active-state/react';
import {Input} from '@workspace/active-ui/components/primitives/input';
import {Label} from '@workspace/active-ui/components/primitives/label';

const CITIES = [
  'Amsterdam',
  'Antwerp',
  'Athens',
  'Berlin',
  'Bern',
  'Bratislava',
  'Brussels',
  'Budapest',
  'Copenhagen',
  'Dublin',
];

/**
 * A stand-in for a request: it answers after a delay that is longer for a
 * shorter query, so an earlier query answers after a later one.
 */
function lookUp(query: string, signal: AbortSignal): Promise<string[]> {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(
      () =>
        resolve(
          CITIES.filter((city) =>
            city.toLowerCase().startsWith(query.toLowerCase()),
          ),
        ),
      900 - Math.min(query.length, 4) * 200,
    );
    signal.addEventListener('abort', () => {
      clearTimeout(timer);
      reject(signal.reason);
    });
  });
}

const scope = new Scope();
const results = new Store<string[]>([]);
const log = new Store<string[]>([]);
const search = new Operation<string, string[]>({
  scope,
  policy: 'latest-wins',
  run: async (query, ctx) => {
    const found = await lookUp(query, ctx.signal);
    // The only way to change state; refused once a newer query superseded this one.
    ctx.commit(() => results.setState(found));
    return found;
  },
});

function describe(query: string, outcome: Outcome<string[]>): string {
  if (outcome.status === 'succeeded')
    return `"${query}": committed ${outcome.value.length} result(s)`;
  if (outcome.status === 'superseded')
    return `"${query}": superseded by #${outcome.by}, nothing committed`;
  if (outcome.status === 'cancelled')
    return `"${query}": cancelled (${outcome.reason})`;
  return `"${query}": failed`;
}

/** An Operation with the latest-wins policy: a late answer cannot overwrite a newer one. */
export default function SearchOperation() {
  const [query, setQuery] = useState('');
  const found = useStore(results);
  const status = useStore(search.status);
  const lines = useStore(log);
  return (
    <div className="flex flex-col gap-3">
      <div className="flex flex-col gap-1.5">
        <Label htmlFor="state-search">City</Label>
        <Input
          id="state-search"
          value={query}
          placeholder="Type quickly: B, Br, Bra…"
          onChange={(event) => {
            const next = event.target.value;
            setQuery(next);
            const invocation = search.invoke(next);
            void invocation.outcome.then((outcome) =>
              log.update((list) =>
                [`#${invocation.id} ${describe(next, outcome)}`, ...list].slice(
                  0,
                  6,
                ),
              ),
            );
          }}
        />
      </div>
      <p className="text-sm" aria-live="polite">
        {status.phase === 'running' ? 'Searching…' : `${found.length} found`}
        {found.length > 0 ? `: ${found.join(', ')}` : ''}
      </p>
      <ol className="text-muted-foreground font-mono text-xs" data-state-log>
        {lines.map((line) => (
          <li key={line}>{line}</li>
        ))}
      </ol>
    </div>
  );
}
```

**Example: An all-or-nothing transfer**

Client: Runs entirely in your browser. Source: `examples/state/transfer-transaction.tsx`.

```tsx
// apps/docs/examples/state/transfer-transaction.tsx
'use client';

import {useState} from 'react';

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

const savings = new Store(100);
const current = new Store(20);
const total = new Derived({
  inputs: [savings, current],
  compute: (a, b) => a + b,
});

/** Moves `amount`; throws, and so reverts both writes, when savings run short. */
function transfer(amount: number) {
  transaction(() => {
    current.update((value) => value + amount);
    savings.update((value) => value - amount);
    if (savings.state < 0) throw new Error('Not enough savings');
  });
}

/** A transaction is all-or-nothing: a failed transfer changes neither account. */
export default function TransferTransaction() {
  const [error, setError] = useState<string | null>(null);
  const a = useStore(savings);
  const b = useStore(current);
  const sum = useStore(total);
  const move = (amount: number) => {
    try {
      transfer(amount);
      setError(null);
    } catch (thrown) {
      setError(thrown instanceof Error ? thrown.message : String(thrown));
    }
  };
  return (
    <div className="flex flex-col gap-3">
      <dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-sm">
        <dt className="text-muted-foreground">Savings</dt>
        <dd data-account="savings">{a}</dd>
        <dt className="text-muted-foreground">Current</dt>
        <dd data-account="current">{b}</dd>
        <dt className="text-muted-foreground">Total</dt>
        <dd>{sum}</dd>
      </dl>
      <div className="flex flex-wrap gap-2">
        <Button variant="outline" size="sm" onClick={() => move(30)}>
          Transfer 30
        </Button>
        <Button variant="outline" size="sm" onClick={() => move(500)}>
          Transfer 500
        </Button>
      </div>
      <p className="min-h-5 text-sm" role="status">
        {error ? `Reverted: ${error}. Neither account changed.` : null}
      </p>
    </div>
  );
}
```
