Getting started
Receive active-state in your repository, declare it, and build a first model with a store, a derived value and a React component.
@workspace/active-state 1.0.1Developmentunreleased, in development in Foundationbaseline ce081dc3
Receive the package#
active-statedepends on no other workspace package, so it travels alone. Ask the maintainer of Foundation for a bundle (on thedevelopmentchannel while it is unreleased), then follow Receiving packages:Terminalnode tools/rps/rps-apply.mjs plan --bundle <bundle-folder> --developmentnode tools/rps/rps-apply.mjs apply --bundle <bundle-folder> --expect-plan <digest> --developmentThe bundle is composed in Foundation with
pnpm rps:transfer bundle --products active-state --channel development --out <folder>.Declare, install and verify#
JSON{ "dependencies": { "@workspace/active-state": "workspace:*", "react": "^19.0.0" }}Terminalpnpm installnode tools/rps/rps-apply.mjs verify-install --from <app-folder>verify-installmust list@workspace/active-stateascurrent. In a Next.js app, add the package totranspilePackages; Vite compiles it as it is.Build a model#
A model is a few objects at module scope (or inside a
Scopeyou own):TypeScriptimport {Derived, shallow, Store} from '@workspace/active-state'; export const lines = new Store<{product: string; price: number}[]>([], { equal: shallow,});export const total = new Derived({ inputs: [lines], compute: (list) => list.reduce((sum, line) => sum + line.price, 0),}); lines.update((list) => [...list, {product: 'Pen', price: 1.25}]);total.state; // 1.25Read it in React#
Import from
/reactin a component file; it repeats the core, so one import is enough:TSX'use client'; import {useStore} from '@workspace/active-state/react';import {total} from './model'; export function Total() { return <output>{useStore(total)}</output>;}The same model, running in your browser:
A store, derived values and useStoreClient
- Notebook: 4.50
1 item, total 4.50
Full source examples/state/cart-total.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> );}
Next#
Stores and derived values explains when a write is published and how equality decides it.