Batches and transactions
Group notifications with batch, or make several writes all-or-nothing with transaction.
@workspace/active-state 1.0.1Developmentunreleased, in development in Foundationbaseline ce081dc3
An all-or-nothing transferClient
- Savings
- 100
- Current
- 20
- Total
- 120
Full source examples/state/transfer-transaction.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> );}batch: one notification for several writes#
TypeScript
import {batch} from '@workspace/active-state';
batch(() => { first.setState('Ada'); last.setState('Lovelace');}); // subscribers run once, after both writesWrites inside a batch are readable at once and published together when the outermost batch ends. A batch never reverts anything: if the callback throws, its writes stay, are published, and the error is rethrown.
transaction: all or nothing#
TypeScript
import {transaction} from '@workspace/active-state';
transaction(() => { current.update((value) => value + amount); savings.update((value) => value - amount); if (savings.state < 0) throw new Error('Not enough savings');});- Reads inside see the transaction's own writes, for stores and derived values alike.
- When the callback throws, every store and derived value it touched is restored, nothing is published, and the first error is rethrown to you.
- A nested
transactionjoins the open one; there are no savepoints. - Only local state is reverted: a request, a DOM change or a copy in another variable is not.
- Structural calls (subscribing, disposing, creating effects or operations) are refused inside a
transaction;
invoke()waits until it commits.
Both are synchronous: a callback that returns a promise is refused with a TypeError.