tldraw/tldraw
@tldraw/state
Active contributors: David Sheldrick, Steve Ruiz
Purpose
@tldraw/state is a reactive signals library. Inspired by S.js / SolidJS / MobX, it provides Atom (writable signal), Computed (derived signal that auto-tracks dependencies), reactions, and transactions. It is the foundation that @tldraw/store and @tldraw/editor are built on, and it ships as its own npm package for downstream use.
Directory layout
packages/state/
├── package.json
└── src/
├── index.ts
└── lib/
├── Atom.ts # writable atoms
├── Computed.ts # derived computeds
├── EffectScheduler.ts # reactor scheduling
├── HistoryBuffer.ts # change history within a single signal
├── ArraySet.ts # internal sparse set
├── transactions.ts # transact, deferAfterTransaction
├── capture.ts # tracking captureStack management
├── localStorageAtom.ts # atom backed by localStorage
├── helpers.ts
├── isComputed.ts
├── isSignal.ts
├── types.ts
├── constants.ts
└── warnings.tsKey abstractions
| Type | File | Purpose |
|---|---|---|
Atom<T> |
packages/state/src/lib/Atom.ts |
Writable reactive value. atom('name', initial). |
Computed<T> |
packages/state/src/lib/Computed.ts |
Derived value with auto-tracked dependencies. computed('name', () => ...). |
Reactor |
packages/state/src/lib/EffectScheduler.ts |
Side-effecting reaction. reactor('name', () => ...). |
react() |
packages/state/src/lib/EffectScheduler.ts |
Helper that creates a reactor and starts it. |
transact() |
packages/state/src/lib/transactions.ts |
Batches mutations so subscribers see one update. |
unsafe__withoutCapture() |
packages/state/src/lib/capture.ts |
Read inside a computed without tracking. |
Signal<T> |
packages/state/src/lib/types.ts |
Common interface (get, lastChangedEpoch, …) implemented by both atoms and computeds. |
localStorageAtom() |
packages/state/src/lib/localStorageAtom.ts |
Atom whose value is mirrored to localStorage. |
How it works
graph LR
Atom -->|read inside| Computed
Computed -->|read inside| Reactor
Atom -->|set| Transaction
Transaction -->|on commit| Computed
Transaction -->|on commit| ReactorReads inside computed() build a dependency graph. When an atom changes, the change is propagated through the graph; downstream computeds invalidate lazily and reactors run synchronously at the end of the current transaction. Writes outside a transact are an implicit transaction of size one.
The library is small, fast, and stable — it is one of the longest-untouched corners of the repo.
React integration
Lives in a separate package: @tldraw/state-react. The most-used hooks are useValue (subscribe to a signal in a component), useComputed, useStateTracking, and track (HOC for fine-grained re-renders).
Usage examples
import { atom, computed, react, transact } from '@tldraw/state';
const count = atom('count', 0);
const doubled = computed('doubled', () => count.get() * 2);
react('log', () => {
console.log('count is', count.get(), 'doubled is', doubled.get());
});
transact(() => {
count.set(1);
count.set(2); // only one log
});Integration points
- Imports. None at runtime (no dependencies).
- Importers.
@tldraw/store,@tldraw/state-react,@tldraw/editor(transitively re-exports).
Entry points for modification
- Performance work lives in
Computed.ts(epoch tracking) andEffectScheduler.ts(queueing). Both are well-tested. - New helpers belong in
helpers.tsif generic, or in their own file if they have a meaningful surface.
Related
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.