### Install Dependencies with Bun or NPM
Source: https://github.com/legendapp/legend-state/blob/main/AGENTS.md
Use `bun install` for dependency management, falling back to `npm install` if necessary.
```bash
bun install
or
npm install
```
--------------------------------
### Observable State Management in React
Source: https://github.com/legendapp/legend-state/blob/main/README.md
Demonstrates basic observable creation, getting and setting values, computed observables, and observing changes. Use this for managing global or component-level state in React applications.
```jsx
import { observable, observe } from "@legendapp/state"
import { observer } from "@legendapp/state/react"
const settings$ = observable({ theme: 'dark' })
// get returns the raw data
settings$.theme.get() // 'dark'
// set sets
settings$.theme.set('light')
// Computed observables with just a function
const isDark$ = observable(() => settings$.theme.get() === 'dark')
// observing contexts re-run when tracked observables change
observe(() => {
console.log(settings$.theme.get())
})
const Component = observer(function Component() {
const theme = state$.settings.theme.get()
return
Theme: {theme}
})
```
--------------------------------
### Create pre-configured sync factories with configureSynced
Source: https://context7.com/legendapp/legend-state/llms.txt
Use `configureSynced` to create a version of `synced` with default options baked in, useful for sharing configurations. This example creates a project-wide fetch synced with auth headers and local persistence.
```typescript
import { configureSynced } from '@legendapp/state/sync';
import { syncedFetch } from '@legendapp/state/sync-plugins/fetch';
import { ObservablePersistLocalStorage } from '@legendapp/state/persist-plugins/local-storage';
// Create a project-wide fetch synced with auth headers and local persist
const mySyncedFetch = configureSynced(syncedFetch, {
getInit: { headers: { Authorization: `Bearer ${getToken()}` } },
persist: { plugin: ObservablePersistLocalStorage },
retry: { infinite: true },
});
const posts$ = observable(mySyncedFetch({ get: '/api/posts', set: '/api/posts' }));
const comments$ = observable(mySyncedFetch({ get: '/api/comments' }));
```
--------------------------------
### HTTP fetch sync plugin with syncedFetch
Source: https://context7.com/legendapp/legend-state/llms.txt
The `syncedFetch` plugin syncs an observable with a REST endpoint. It supports GET and SET URLs, custom request init, response type, and an `onSaved` callback. Reading the observable triggers the GET fetch, and setting a value triggers the SET fetch after debouncing.
```typescript
import { observable } from '@legendapp/state';
import { syncedFetch } from '@legendapp/state/sync-plugins/fetch';
const article$ = observable(
syncedFetch<{ id: number; title: string; body: string }>({
get: 'https://jsonplaceholder.typicode.com/posts/1',
set: 'https://jsonplaceholder.typicode.com/posts/1',
setInit: { method: 'PUT', headers: { 'Content-Type': 'application/json' } },
valueType: 'json',
onSaved: ({ saved, input }) => {
console.log('Server confirmed save:', input.id);
return saved; // merge saved response back in
},
persist: { name: 'article-1' },
}),
);
// Reading triggers the GET fetch
console.log(article$.title.get()); // undefined → then 'sunt aut facere...'
article$.title.set('My new title'); // triggers the PUT fetch after 0ms debounce
```
--------------------------------
### Wait for Conditions with when() and whenReady()
Source: https://context7.com/legendapp/legend-state/llms.txt
Use `when` to get a Promise that resolves when a predicate is truthy, optionally running an effect. `whenReady` additionally ensures objects/arrays are non-empty. Both support array predicates.
```typescript
import { observable, when, whenReady } from '@legendapp/state';
const auth$ = observable({ isLoaded: false, user: null as any });
// As a Promise
await when(() => auth$.isLoaded.get());
console.log('Auth is loaded');
// With an effect callback
when(
() => auth$.user.get(),
(user) => console.log('Logged in as', user.name),
);
// Array predicates — waits for ALL to be truthy
const db$ = observable({ connected: false });
await when([() => auth$.isLoaded.get(), () => db$.connected.get()]);
console.log('Both ready');
// whenReady — empty objects/arrays don't count as ready
const items$ = observable([]);
await whenReady(items$);
console.log('Items array is non-empty:', items$.get());
```
--------------------------------
### Access sync/persist state metadata with syncState
Source: https://context7.com/legendapp/legend-state/llms.txt
Use `syncState` to get an observable with metadata about the sync/persist lifecycle. It also exposes `sync()` and `reset()` methods for manual control.
```typescript
import { observable } from '@legendapp/state';
import { syncState } from '@legendapp/state';
import { synced } from '@legendapp/state/sync';
const todos$ = observable(synced({
get: async () => fetch('/api/todos').then(r => r.json()),
set: async ({ value }) => fetch('/api/todos', { method: 'PUT', body: JSON.stringify(value) }),
persist: { name: 'todos' },
}));
const state$ = syncState(todos$);
console.log(state$.isLoaded.get()); // false initially
console.log(state$.numPendingGets.get()); // 1 while fetching
// Manually trigger sync
await state$.sync();
// Reset persist and re-fetch
await state$.reset();
```
--------------------------------
### syncedFetch(props)
Source: https://context7.com/legendapp/legend-state/llms.txt
An HTTP fetch sync plugin that syncs an observable with a REST endpoint. It supports GET and SET URLs, custom request init, response type, and an `onSaved` callback for processing the server response.
```APIDOC
## syncedFetch(props)
### Description
HTTP fetch sync plugin. Syncs an observable with a REST endpoint. Supports GET and SET URLs, custom request init, response type, and an `onSaved` callback for processing the server response.
### Parameters
#### Request Body
- **props** (object) - Required - Configuration properties for the fetch sync.
- **get** (string) - Required - The URL for GET requests.
- **set** (string) - Required - The URL for SET requests.
- **setInit** (object) - Optional - Request init options for SET requests.
- **method** (string) - HTTP method (e.g., 'PUT').
- **headers** (object) - Request headers.
- **valueType** (string) - Optional - The expected response type ('json', 'text', etc.). Defaults to 'json'.
- **onSaved** (function) - Optional - Callback function executed after a successful save.
- **saved** - The data saved by the server.
- **input** - The input data sent to the server.
- **persist** (object) - Optional - Configuration for local persistence.
- **name** (string) - Required - The name for the persistence key.
### Request Example
```ts
import { observable } from '@legendapp/state';
import { syncedFetch } from '@legendapp/state/sync-plugins/fetch';
const article$ = observable(
syncedFetch<{ id: number; title: string; body: string(>({
get: 'https://jsonplaceholder.typicode.com/posts/1',
set: 'https://jsonplaceholder.typicode.com/posts/1',
setInit: { method: 'PUT', headers: { 'Content-Type': 'application/json' } },
valueType: 'json',
onSaved: ({ saved, input }) => {
console.log('Server confirmed save:', input.id);
return saved; // merge saved response back in
},
persist: { name: 'article-1' },
}),
);
// Reading triggers the GET fetch
console.log(article$.title.get()); // undefined → then 'sunt aut facere...'
article$.title.set('My new title'); // triggers the PUT fetch after 0ms debounce
```
```
--------------------------------
### Low-Level Bidirectional Link with linked()
Source: https://context7.com/legendapp/legend-state/llms.txt
Use `linked` to create a primitive for custom observable data sources with optional `get` and `set` functions, supporting async operations and subscriptions. This is the basis for `computed` and `synced`.
```typescript
import { observable, linked } from '@legendapp/state';
// Two-way link to localStorage
const theme$ = observable(
linked({
get: () => localStorage.getItem('theme') ?? 'dark',
set: ({ value }) => localStorage.setItem('theme', value),
}),
);
theme$.set('light');
console.log(localStorage.getItem('theme')); // 'light'
console.log(theme$.get()); // 'light'
```
--------------------------------
### Undo/Redo History with `undoRedo` Helper
Source: https://context7.com/legendapp/legend-state/llms.txt
The `undoRedo` helper attaches undo/redo capability to any observable. It returns functions to perform undo/redo actions, observables for available counts, and a method to get the history. It does not record remote or persisted changes.
```ts
import { observable } from '@legendapp/state';
import { undoRedo } from '@legendapp/state/helpers/undoRedo';
const doc$ = observable({ title: 'Hello', body: 'World' });
const { undo, redo, undos$, redos$, getHistory } = undoRedo(doc$, { limit: 50 });
doc$.title.set('Hi');
doc$.body.set('Everyone');
console.log(undos$.get()); // 2
console.log(redos$.get()); // 0
undo();
console.log(doc$.title.get()); // 'Hi' — body reverted
console.log(undos$.get()); // 1
redo();
console.log(doc$.body.get()); // 'Everyone' — redone
```
--------------------------------
### `linked(options)`
Source: https://context7.com/legendapp/legend-state/llms.txt
Low-level bidirectional link primitive. The building block used by `computed` and `synced`. Creates a linked observable that resolves via custom `get` and optionally `set` functions, supporting any async or subscription-based data source.
```APIDOC
## `linked(options)` — Low-level bidirectional link primitive
The building block used by `computed` and `synced`. Creates a linked observable that resolves via custom `get` and optionally `set` functions, supporting any async or subscription-based data source.
```ts
import { observable, linked } from '@legendapp/state';
// Two-way link to localStorage
const theme$ = observable(
linked({
get: () => localStorage.getItem('theme') ?? 'dark',
set: ({ value }) => localStorage.setItem('theme', value),
}),
);
theme$.set('light');
console.log(localStorage.getItem('theme')); // 'light'
console.log(theme$.get()); // 'light'
```
```
--------------------------------
### Build Project Including Checks and Bundling
Source: https://github.com/legendapp/legend-state/blob/main/AGENTS.md
Perform a full build process that includes linting, formatting checks, tests, and bundling with tsup.
```bash
npm run build
```
--------------------------------
### Run Unit Tests with Bun or Jest
Source: https://github.com/legendapp/legend-state/blob/main/AGENTS.md
Execute the local unit test suite using Bun. `npm test` is available for CI parity with Jest.
```bash
bun test
npm test
```
--------------------------------
### Run Post-build Script
Source: https://github.com/legendapp/legend-state/blob/main/CLAUDE.md
Execute a post-build script after the tsup bundling process. This script performs actions necessary after the main build artifacts are generated.
```bash
bun run posttsup.ts
```
--------------------------------
### Run Tests with Timeout for Quick Feedback
Source: https://github.com/legendapp/legend-state/blob/main/AGENTS.md
Execute tests using Bun with a specified timeout, useful for rapid local feedback during development.
```bash
bun test --timeout 50
```
--------------------------------
### Synced and Persistent State Management
Source: https://github.com/legendapp/legend-state/blob/main/README.md
Shows how to configure observable state with persistence and synchronization using `syncedKeel`. This is useful for building local-first applications that require optimistic updates, retries, and minimal diff syncing.
```js
const state$ = observable({
users: syncedKeel({
list: queries.getUsers,
create: mutations.createUsers,
update: mutations.updateUsers,
delete: mutations.deleteUsers,
persist: { name: 'users', retrySync: true },
debounceSet: 500,
retry: {
infinite: true,
},
changesSince: 'last-sync',
}),
// direct link to my user within the users observable
me: () => state$.users['myuid']
})
observe(() => {
// get() activates through to state$.users and starts syncing.
// it updates itself and re-runs observers when name changes
const name = me$.name.get()
})
// Setting a value goes through to state$.users and saves update to server
me$.name.set('Annyong')
```
--------------------------------
### computed(get, set?)
Source: https://context7.com/legendapp/legend-state/llms.txt
Creates a derived observable whose value is computed from other observables. It can be read-only or two-way. Computations run lazily and only when the computed value is observed and its dependencies change. Supports asynchronous computations from Promises.
```APIDOC
## computed(get, set?) — Derived observable
Creates a read-only (or two-way) computed observable whose value is derived from other observables. The computation re-runs lazily — only when the computed is observed and a dependency changes.
```ts
import { observable, computed } from '@legendapp/state';
const store$ = observable({ firstName: 'John', lastName: 'Doe', price: 10, qty: 3 });
// Read-only computed
const fullName$ = computed(() => `${store$.firstName.get()} ${store$.lastName.get()}`);
console.log(fullName$.get()); // 'John Doe'
store$.firstName.set('Jane');
console.log(fullName$.get()); // 'Jane Doe'
// Two-way computed (writable derived value)
const total$ = computed(
() => store$.price.get() * store$.qty.get(),
(value) => store$.price.set(value / store$.qty.get()),
);
console.log(total$.get()); // 30
total$.set(60); // sets price to 20
console.log(store$.price.get()); // 20
// Async computed from a Promise
const user$ = computed(async () => {
const res = await fetch('/api/me');
return res.json();
});
// user$.get() is undefined until the promise resolves
```
```
--------------------------------
### Fine-grained Reactivity with Memo
Source: https://github.com/legendapp/legend-state/blob/main/README.md
Illustrates how to use `useObservable` and `Memo` for fine-grained reactivity in React. This approach ensures that only the necessary parts of the UI update, improving performance by minimizing component re-renders.
```jsx
function FineGrained() {
const count$ = useObservable(0)
useInterval(() => {
count$.set(v => v + 1)
}, 600)
// The text updates itself so the component doesn't re-render
return (
Count: {count$}
)
}
```
--------------------------------
### Run Jest Test Suite
Source: https://github.com/legendapp/legend-state/blob/main/CLAUDE.md
Execute the Jest test suite for the project. This is the standard command for running tests.
```bash
npm test
```
```bash
jest
```
--------------------------------
### Build with tsup Bundler
Source: https://github.com/legendapp/legend-state/blob/main/CLAUDE.md
Build project artifacts using the tsup bundler to generate distribution files. This command is part of the build process.
```bash
bunx tsup
```
--------------------------------
### `event()`
Source: https://context7.com/legendapp/legend-state/llms.txt
Creates a lightweight event emitter built on an observable number counter. Use `.fire()` to dispatch, `.on(cb)` to subscribe, and pass it to `when` or `observe` to react to events.
```APIDOC
## `event()` — Observable event bus
Creates a lightweight event emitter built on an observable number counter. Use `.fire()` to dispatch, `.on(cb)` to subscribe, and pass it to `when` or `observe` to react to events.
```ts
import { event, observe, when } from '@legendapp/state';
const submitEvent = event();
// Subscribe with .on (returns a dispose function)
const dispose = submitEvent.on(() => console.log('Submitted!'));
submitEvent.fire(); // logs: Submitted!
// React inside observe
observe(() => {
submitEvent.get(); // tracks the event
console.log('Event fired, count:', submitEvent.get());
});
// Wait for event with `when`
when(submitEvent, () => console.log('First fire happened'));
submitEvent.fire();
dispose(); // unsubscribe
```
```
--------------------------------
### Check Bundle Size
Source: https://github.com/legendapp/legend-state/blob/main/AGENTS.md
Monitor the bundle footprint of the generated artifacts before a release or significant change.
```bash
npm run checksize
```
--------------------------------
### Verify Code Formatting with Prettier
Source: https://github.com/legendapp/legend-state/blob/main/AGENTS.md
Check if the code adheres to Prettier formatting standards. Append `:write` to auto-fix.
```bash
npm run format:check
# or
npm run format:check:write
```
--------------------------------
### configureSynced(fnOrOptions, defaults?)
Source: https://context7.com/legendapp/legend-state/llms.txt
Creates pre-configured sync factories by applying default options to a sync function or options object. This is useful for sharing a base configuration across multiple observables.
```APIDOC
## configureSynced(fnOrOptions, defaults?)
### Description
Create pre-configured sync factories. Creates a version of `synced` (or any sync plugin) with default options baked in, useful for sharing a base configuration across many observables.
### Parameters
#### Request Body
- **fnOrOptions** (function | object) - Required - The sync function or options object to configure.
- **defaults** (object) - Optional - Default options to apply.
### Request Example
```ts
import { configureSynced } from '@legendapp/state/sync';
import { syncedFetch } from '@legendapp/state/sync-plugins/fetch';
import { ObservablePersistLocalStorage } from '@legendapp/state/persist-plugins/local-storage';
// Create a project-wide fetch synced with auth headers and local persist
const mySyncedFetch = configureSynced(syncedFetch, {
getInit: { headers: { Authorization: `Bearer ${getToken()}` } },
persist: { plugin: ObservablePersistLocalStorage },
retry: { infinite: true },
});
const posts$ = observable(mySyncedFetch({ get: '/api/posts', set: '/api/posts' }));
const comments$ = observable(mySyncedFetch({ get: '/api/comments' }));
```
```
--------------------------------
### `when(predicate, effect?)` / `whenReady(predicate, effect?)`
Source: https://context7.com/legendapp/legend-state/llms.txt
Returns a Promise that resolves once the predicate becomes truthy. Optionally runs an `effect` when it resolves. `whenReady` also requires that objects/arrays are non-empty. Both accept an array of predicates.
```APIDOC
## `when(predicate, effect?)` / `whenReady(predicate, effect?)` — Wait for a condition
Returns a Promise that resolves once the predicate becomes truthy. Optionally runs an `effect` when it resolves. `whenReady` also requires that objects/arrays are non-empty. Both accept an array of predicates.
```ts
import { observable, when, whenReady } from '@legendapp/state';
const auth$ = observable({ isLoaded: false, user: null as any });
// As a Promise
await when(() => auth$.isLoaded.get());
console.log('Auth is loaded');
// With an effect callback
when(
() => auth$.user.get(),
(user) => console.log('Logged in as', user.name),
);
// Array predicates — waits for ALL to be truthy
const db$ = observable({ connected: false });
await when([() => auth$.isLoaded.get(), () => db$.connected.get()]);
console.log('Both ready');
// whenReady — empty objects/arrays don't count as ready
const items$ = observable([]);
await whenReady(items$);
console.log('Items array is non-empty:', items$.get());
```
```
--------------------------------
### `observe(selector, reaction?, options?)`
Source: https://context7.com/legendapp/legend-state/llms.txt
Runs a function immediately and re-runs it whenever any observable accessed inside changes. Returns a dispose function. Supports a separate reaction callback for responding only to changes.
```APIDOC
## `observe(selector, reaction?, options?)` — Reactive side-effect runner
Runs a function immediately and re-runs it whenever any observable accessed inside changes. Returns a dispose function. Supports a separate reaction callback for responding only to changes (not the initial run).
```ts
import { observable, observe } from '@legendapp/state';
const pos$ = observable({ x: 0, y: 0 });
// Runs immediately, re-runs whenever pos$.x or pos$.y change
const dispose = observe(() => {
console.log('Position:', pos$.x.get(), pos$.y.get());
});
// logs: Position: 0 0
pos$.x.set(5);
// logs: Position: 5 0
// Cleanup / stop observing
dispose();
// Two-argument form: selector + reaction (reaction only fires on changes, not initial run)
const counter$ = observable(0);
observe(
() => counter$.get(),
(e) => {
console.log(`Changed from ${e.previous} to ${e.value}`);
if (e.value > 10) e.cancel = true; // stop observing
},
);
// Cleanup with e.onCleanup
observe((e) => {
const id = setInterval(() => console.log(counter$.get()), 1000);
e.onCleanup = () => clearInterval(id);
});
```
```
--------------------------------
### Observable Event Bus with event()
Source: https://context7.com/legendapp/legend-state/llms.txt
Create an event emitter using `event()`. Use `.fire()` to dispatch, `.on(cb)` to subscribe (returning a dispose function), and integrate with `observe` or `when` to react to events.
```typescript
import { event, observe, when } from '@legendapp/state';
const submitEvent = event();
// Subscribe with .on (returns a dispose function)
const dispose = submitEvent.on(() => console.log('Submitted!'));
submitEvent.fire(); // logs: Submitted!
// React inside observe
observe(() => {
submitEvent.get(); // tracks the event
console.log('Event fired, count:', submitEvent.get());
});
// Wait for event with `when`
when(submitEvent, () => console.log('First fire happened'));
submitEvent.fire();
dispose(); // unsubscribe
```
--------------------------------
### Running CRUD Tests
Source: https://github.com/legendapp/legend-state/blob/main/plans/1-fix-crud-retry.md
Commands to execute the CRUD and Keel test suites, along with type checking, to validate the implementation of the CRUD create retry fix.
```bash
bun test tests/crud.test.ts --timeout 200
bun test tests/keel.test.ts --timeout 200
npm run typecheck
```
--------------------------------
### useObserve
Source: https://context7.com/legendapp/legend-state/llms.txt
A React hook version of `observe`. It sets up a reactive observation that automatically disposes when the component unmounts. It can take a selector function and an optional reaction function and options.
```APIDOC
## `useObserve(selector, reaction?, options?)` — React hook for `observe`
A React hook version of `observe`. Sets up a reactive observation that auto-disposes when the component unmounts.
```tsx
import { observable } from '@legendapp/state';
import { observer, useObserve } from '@legendapp/state/react';
const socket$ = observable({ connected: false, lastMessage: '' });
const ChatMonitor = observer(function ChatMonitor() {
// Runs immediately and re-runs when socket$.connected changes
useObserve(
() => socket$.connected.get(),
({ value, previous }) => {
if (value && !previous) console.log('Socket reconnected');
},
);
// Observe with cleanup
useObserve((e) => {
const id = socket$.lastMessage.get(); // track
document.title = `New message: ${id}`;
e.onCleanup = () => { document.title = 'App'; };
});
return
;
});
```
```
--------------------------------
### Check Core Bundle Size
Source: https://github.com/legendapp/legend-state/blob/main/CLAUDE.md
Check the gzipped bundle size specifically for the core Legend-State package. Useful for isolating performance concerns.
```bash
npm run checksize:core
```
--------------------------------
### Check Sync Package Bundle Size
Source: https://github.com/legendapp/legend-state/blob/main/CLAUDE.md
Check the gzipped bundle size for the synchronization package of Legend-State. Useful for analyzing the performance of sync features.
```bash
npm run checksize:sync
```
--------------------------------
### Supabase Sync Plugin with Real-time Subscriptions
Source: https://context7.com/legendapp/legend-state/llms.txt
Integrates with Supabase for full CRUD operations, including real-time subscriptions. Configure collection, select queries, filters, and persistence options. The observable is keyed by row ID for individual record access and mutation.
```typescript
import { observable } from '@legendapp/state';
import { syncedSupabase } from '@legendapp/state/sync-plugins/supabase';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!);
const messages$ = observable(
syncedSupabase({
supabase,
collection: 'messages',
select: (query) => query.eq('room_id', 'room-1'),
filter: 'room_id=eq.room-1',
realtime: { filter: 'room_id=eq.room-1' },
persist: { name: 'messages-room-1', retrySync: true },
retry: { infinite: true },
}),
);
// messages$ is keyed by row id — access and mutate individual records
const msg = messages$['msg-123'].get();
messages$['msg-123'].text.set('Updated text'); // syncs to Supabase
```
--------------------------------
### Check Prettier Formatting
Source: https://github.com/legendapp/legend-state/blob/main/CLAUDE.md
Check code formatting against Prettier rules without making changes. This is for verifying code style adherence.
```bash
npm run format:check
```
--------------------------------
### Run Bun Tests with Timeout
Source: https://github.com/legendapp/legend-state/blob/main/CLAUDE.md
Execute Bun tests with a specified timeout. Useful for ensuring tests complete within a reasonable time.
```bash
bun test
```
```bash
npm run buntest
```
--------------------------------
### `batch(fn)` / `beginBatch()` / `endBatch()`
Source: https://context7.com/legendapp/legend-state/llms.txt
Group mutations to wrap multiple observable mutations so all listeners are notified once after all changes complete rather than after each individual change. `beginBatch` / `endBatch` provide manual control for async scenarios.
```APIDOC
## `batch(fn)` / `beginBatch()` / `endBatch()` — Group mutations
Wraps multiple observable mutations so all listeners are notified once after all changes complete rather than after each individual change. `beginBatch` / `endBatch` provide manual control for async scenarios.
```ts
import { observable, batch, beginBatch, endBatch } from '@legendapp/state';
const store$ = observable({ a: 1, b: 2, c: 3 });
// Single notification after all three sets
batch(() => {
store$.a.set(10);
store$.b.set(20);
store$.c.set(30);
});
// Manual begin/end
beginBatch();
store$.a.set(100);
store$.b.set(200);
endBatch(); // listeners fire here
```
```
--------------------------------
### Format Code with Prettier
Source: https://github.com/legendapp/legend-state/blob/main/CLAUDE.md
Format code using Prettier to ensure consistent styling across the project. This command automatically applies formatting rules.
```bash
npm run format:write
```
--------------------------------
### syncState(obs)
Source: https://context7.com/legendapp/legend-state/llms.txt
Provides access to the sync/persist state metadata of an observable. It returns an observable containing metadata like `isLoaded`, `isPersistLoaded`, `numPendingGets`, and `numPendingSets`, and also exposes `sync()` and `reset()` methods.
```APIDOC
## syncState(obs)
### Description
Returns an observable containing metadata about the sync/persist lifecycle of an observable (e.g., `isLoaded`, `isPersistLoaded`, `numPendingGets`, `numPendingSets`). Also exposes `sync()` and `reset()` methods.
### Parameters
#### Path Parameters
- **obs** (observable) - Required - The observable to sync state metadata for.
### Methods
- **sync()**: Manually trigger a sync operation.
- **reset()**: Reset persist and re-fetch data.
### Request Example
```ts
import { observable } from '@legendapp/state';
import { syncState } from '@legendapp/state';
import { synced } from '@legendapp/state/sync';
const todos$ = observable(synced({
get: async () => fetch('/api/todos').then(r => r.json()),
set: async ({ value }) => fetch('/api/todos', { method: 'PUT', body: JSON.stringify(value) }),
persist: { name: 'todos' },
}));
const state$ = syncState(todos$);
console.log(state$.isLoaded.get()); // false initially
console.log(state$.numPendingGets.get()); // 1 while fetching
// Manually trigger sync
await state$.sync();
// Reset persist and re-fetch
await state$.reset();
```
```
--------------------------------
### Run Reactive Side-Effects with observe()
Source: https://context7.com/legendapp/legend-state/llms.txt
Use `observe` to run a function immediately and re-run it when observables change. The two-argument form allows a reaction callback that only fires on changes. Use `e.onCleanup` for cleanup logic.
```typescript
import { observable, observe } from '@legendapp/state';
const pos$ = observable({ x: 0, y: 0 });
// Runs immediately, re-runs whenever pos$.x or pos$.y change
const dispose = observe(() => {
console.log('Position:', pos$.x.get(), pos$.y.get());
});
// logs: Position: 0 0
pos$.x.set(5);
// logs: Position: 5 0
// Cleanup / stop observing
dispose();
// Two-argument form: selector + reaction (reaction only fires on changes, not initial run)
const counter$ = observable(0);
observe(
() => counter$.get(),
(e) => {
console.log(`Changed from ${e.previous} to ${e.value}`);
if (e.value > 10) e.cancel = true; // stop observing
},
);
// Cleanup with e.onCleanup
observe((e) => {
const id = setInterval(() => console.log(counter$.get()), 1000);
e.onCleanup = () => clearInterval(id);
});
```
--------------------------------
### Check React Bindings Bundle Size
Source: https://github.com/legendapp/legend-state/blob/main/CLAUDE.md
Check the gzipped bundle size for the React bindings of Legend-State. Helps in understanding the impact of React integration on bundle size.
```bash
npm run checksize:react
```
--------------------------------
### Check Code Linting with ESLint
Source: https://github.com/legendapp/legend-state/blob/main/AGENTS.md
Enforce ESLint rules across the source and test directories to maintain code quality.
```bash
npm run lint:check
```
--------------------------------
### Pending Create State Model
Source: https://github.com/legendapp/legend-state/blob/main/plans/1-fix-crud-retry.md
Defines the structure for tracking pending create operations, including failure status, change details, and promise references. This replaces a bare Set for more granular state management.
```typescript
type PendingCreate = {
hasFailed: boolean;
change?: ChangeWithPathStr;
promise?: Promise;
cancelled?: boolean;
};
```
--------------------------------
### observer(component)
Source: https://context7.com/legendapp/legend-state/llms.txt
Wraps a function component to make it reactive. Any `observable.get()` calls inside the render function will automatically subscribe to changes and trigger re-renders only when accessed values change.
```APIDOC
## observer(component)
### Description
Wraps a function component so that any `observable.get()` calls inside its render automatically subscribe to changes and trigger re-renders only when accessed values change.
### Usage
```tsx
import { observable } from '@legendapp/state';
import { observer } from '@legendapp/state/react';
const store$ = observable({ count: 0, user: { name: 'Alice' } });
// This component re-renders ONLY when store$.count or store$.user.name change
const Counter = observer(function Counter() {
const count = store$.count.get();
const name = store$.user.name.get();
return (
{name}'s count: {count}
);
});
```
### Parameters
- `component`: The function component to wrap.
```
--------------------------------
### useObservable(initialValue, deps?)
Source: https://context7.com/legendapp/legend-state/llms.txt
Creates an observable scoped to a component's lifetime. It returns the same observable instance across renders and can accept a plain value, a function (computed), or a Promise as the initial value.
```APIDOC
## useObservable(initialValue, deps?)
### Description
Creates an observable scoped to a component's lifetime. Stable across renders — the same observable instance is returned every render. Accepts a plain value, a function (computed), or a Promise.
### Usage
```tsx
import { observer, useObservable } from '@legendapp/state/react';
const TodoList = observer(function TodoList() {
const todos$ = useObservable([
{ id: 1, text: 'Learn Legend-State', done: false },
]);
const filter$ = useObservable<'all' | 'done' | 'pending'>('all');
const addTodo = (text: string) =>
todos$.push({ id: Date.now(), text, done: false });
return (
);
});
```
### Parameters
- `initialValue`: The initial value for the observable. Can be a value, a function, or a Promise.
- `deps?`: Optional dependencies array for computed observables.
```
--------------------------------
### useObserve Hook for Reactive Observations
Source: https://context7.com/legendapp/legend-state/llms.txt
Use useObserve to set up reactive observations within React components. It auto-disposes when the component unmounts. The reaction function receives an event object with `value` and `previous` properties.
```tsx
import { observable } from '@legendapp/state';
import { observer, useObserve } from '@legendapp/state/react';
const socket$ = observable({ connected: false, lastMessage: '' });
const ChatMonitor = observer(function ChatMonitor() {
// Runs immediately and re-runs when socket$.connected changes
useObserve(
() => socket$.connected.get(),
({ value, previous }) => {
if (value && !previous) console.log('Socket reconnected');
},
);
// Observe with cleanup
useObserve((e) => {
const id = socket$.lastMessage.get(); // track
document.title = `New message: ${id}`;
e.onCleanup = () => { document.title = 'App'; };
});
return
;
});
```
--------------------------------
### synced(options)
Source: https://context7.com/legendapp/legend-state/llms.txt
The primary entry point for connecting an observable to local persistence and/or a remote data source. It handles optimistic updates, retry on failure, change diffing, and local-first caching automatically.
```APIDOC
## synced(options)
### Description
Sync and persist an observable. The primary entry point for connecting an observable to local persistence and/or a remote data source. Handles optimistic updates, retry on failure, change diffing, and local-first caching automatically.
### Parameters
#### Request Body
- **options** (object) - Required - Configuration options for syncing.
- **get** (function | string) - Function or URL to fetch data.
- **set** (function | string) - Function or URL to set data.
- **persist** (object) - Configuration for local persistence.
- **name** (string) - Required - The name for the persistence key.
- **retrySync** (boolean) - Optional - Whether to retry sync operations.
- **retry** (object) - Configuration for retrying failed network calls.
- **infinite** (boolean) - Optional - Retry indefinitely.
- **backoff** (string) - Optional - Backoff strategy (e.g., 'exponential').
- **maxDelay** (number) - Optional - Maximum delay between retries.
- **debounceSet** (number) - Optional - Debounce time in milliseconds for set calls.
### Request Example
```ts
import { observable } from '@legendapp/state';
import { synced, configureObservableSync } from '@legendapp/state/sync';
import { ObservablePersistLocalStorage } from '@legendapp/state/persist-plugins/local-storage';
// Configure global defaults once at app startup
configureObservableSync({
persist: { plugin: ObservablePersistLocalStorage },
});
const profile$ = observable(
synced({
// Fetch from server
get: async () => {
const res = await fetch('/api/profile');
if (!res.ok) throw new Error(res.statusText);
return res.json();
},
// Push changes to server
set: async ({ value, changes }) => {
await fetch('/api/profile', {
method: 'PATCH',
body: JSON.stringify(changes),
});
},
// Local persistence key
persist: { name: 'profile', retrySync: true },
// Retry failed network calls indefinitely
retry: { infinite: true, backoff: 'exponential', maxDelay: 30000 },
// Debounce set calls
debounceSet: 500,
}),
);
// Reading activates sync — fetches remotely, loads from cache first
console.log(profile$.name.get()); // undefined → cached value → remote value
```
```
--------------------------------
### Fix ESLint Issues
Source: https://github.com/legendapp/legend-state/blob/main/CLAUDE.md
Run ESLint and automatically fix any identified code style issues. This command helps maintain code consistency.
```bash
npm run lint:write
```
--------------------------------
### Group Mutations with batch(), beginBatch()/endBatch()
Source: https://context7.com/legendapp/legend-state/llms.txt
Use `batch` or manual `beginBatch`/`endBatch` to group observable mutations. Listeners are notified only once after all changes complete, improving performance in async scenarios.
```typescript
import { observable, batch, beginBatch, endBatch } from '@legendapp/state';
const store$ = observable({ a: 1, b: 2, c: 3 });
// Single notification after all three sets
batch(() => {
store$.a.set(10);
store$.b.set(20);
store$.c.set(30);
});
// Manual begin/end
beginBatch();
store$.a.set(100);
store$.b.set(200);
endBatch(); // listeners fire here
```
--------------------------------
### Pattern-Matched Rendering with ``
Source: https://context7.com/legendapp/legend-state/llms.txt
Use `` for conditional rendering based on an observable's value. It supports `'null'`, `'undefined'`, and a `'default'` fallback key for unmatched states.
```tsx
import { observable } from '@legendapp/state';
import { Switch } from '@legendapp/state/react';
const status$ = observable<'idle' | 'loading' | 'success' | 'error'>('idle');
function StatusDisplay() {
return (
{{
idle: () =>
Ready
,
loading: () =>
Loading…
,
success: () =>
✓ Done
,
error: () =>
✗ Error
,
default: () =>
Unknown state
,
}}
);
}
```
--------------------------------
### Run Bun Tests Silently
Source: https://github.com/legendapp/legend-state/blob/main/CLAUDE.md
Execute Bun tests silently. This command is typically used in CI or build environments where verbose output is not needed.
```bash
npm run buntestsilent
```
--------------------------------
### Local Component Observable with useObservable
Source: https://context7.com/legendapp/legend-state/llms.txt
Creates a component-scoped observable that persists across renders. It can be initialized with a plain value, a computed function, or a Promise. Use this for managing local component state.
```tsx
import { observer, useObservable } from '@legendapp/state/react';
const TodoList = observer(function TodoList() {
const todos$ = useObservable([
{ id: 1, text: 'Learn Legend-State', done: false },
]);
const filter$ = useObservable<'all' | 'done' | 'pending'>('all');
const addTodo = (text: string) =>
todos$.push({ id: Date.now(), text, done: false });
return (
);
});
```
--------------------------------
### Create Lightweight Primitive Observables with `observablePrimitive`
Source: https://context7.com/legendapp/legend-state/llms.txt
Use `observablePrimitive` for lightweight observables optimized for single primitive values. It uses a class internally for better performance with thousands of separate primitive observables. Supports direct setting and functional updates.
```typescript
import { observablePrimitive } from '@legendapp/state';
const count$ = observablePrimitive(0);
count$.set(1);
count$.set(v => v + 1);
console.log(count$.get()); // 2
// Toggle booleans
const isOpen$ = observablePrimitive(false);
isOpen$.toggle();
console.log(isOpen$.get()); // true
```
--------------------------------
### syncedSupabase
Source: https://context7.com/legendapp/legend-state/llms.txt
Provides full CRUD integration with Supabase tables, including real-time subscriptions, date serialization, and typed schema support.
```APIDOC
## syncedSupabase
### Description
Provides full CRUD integration with Supabase tables including real-time subscriptions, date serialization, and typed schema support.
### Usage
```ts
import { observable } from '@legendapp/state';
import { syncedSupabase } from '@legendapp/state/sync-plugins/supabase';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!)
const messages$ = observable(
syncedSupabase({
supabase,
collection: 'messages',
select: (query) => query.eq('room_id', 'room-1'),
filter: 'room_id=eq.room-1',
realtime: { filter: 'room_id=eq.room-1' },
persist: { name: 'messages-room-1', retrySync: true },
retry: { infinite: true },
})
);
// messages$ is keyed by row id — access and mutate individual records
const msg = messages$['msg-123'].get();
messages$['msg-123'].text.set('Updated text'); // syncs to Supabase
```
### Parameters
- `supabase`: Supabase client instance.
- `collection`: The name of the Supabase table.
- `select`: A function to configure the query.
- `filter`: A string filter for the query.
- `realtime`: Configuration for real-time subscriptions.
- `persist`: Configuration for data persistence.
- `retry`: Configuration for retry logic.
```
--------------------------------
### observable(value)
Source: https://context7.com/legendapp/legend-state/llms.txt
Creates a deeply observable state tree from any value. Nested properties are automatically proxied and observable. Use .get() to retrieve raw values and .set() to mutate them. Supports partial updates with .assign() and deletion with .delete().
```APIDOC
## observable(value) — Create a deeply observable state tree
Creates an observable from any value: a plain object, array, primitive, function (computed), or Promise. Every nested property is automatically proxied and observable. Accessing a property returns a child observable; `.get()` retrieves the raw value and `.set()` mutates it.
```ts
import { observable } from '@legendapp/state';
// Plain object — every nested key is independently observable
const store$ = observable({
user: { name: 'Alice', age: 30 },
todos: [{ id: 1, text: 'Buy milk', done: false }],
count: 0,
});
// Read raw values
store$.user.name.get(); // 'Alice'
store$.user.get(); // { name: 'Alice', age: 30 }
store$.todos[0].done.get(); // false
// Write values
store$.user.name.set('Bob');
store$.count.set(v => v + 1); // functional update
store$.todos[0].done.set(true);
// Assign partial updates (merges into the observable)
store$.user.assign({ age: 31 });
// Delete a key
store$.user.name.delete();
// Promise: resolves automatically into the observable
const data$ = observable(fetch('/api/user').then(r => r.json()));
// data$.get() returns undefined until resolved, then the JSON value
// With TypeScript
interface AppState { theme: 'dark' | 'light'; lang: string }
const settings$ = observable({ theme: 'dark', lang: 'en' });
```
```
--------------------------------
### observablePrimitive(value)
Source: https://context7.com/legendapp/legend-state/llms.txt
Creates a lightweight observable optimized for a single primitive value (number, string, boolean). It uses a class internally instead of a Proxy for better performance with a large number of primitive observables. Supports functional updates and a .toggle() method for booleans.
```APIDOC
## observablePrimitive(value) — Lightweight primitive observable
Creates a lightweight observable optimised for a single primitive value (number, string, boolean). Uses a class internally instead of a Proxy, making it faster when you have thousands of separate primitive observables.
```ts
import { observablePrimitive } from '@legendapp/state';
const count$ = observablePrimitive(0);
count$.set(1);
count$.set(v => v + 1);
console.log(count$.get()); // 2
// Toggle booleans
const isOpen$ = observablePrimitive(false);
isOpen$.toggle();
console.log(isOpen$.get()); // true
```
```
--------------------------------
### Show Component
Source: https://context7.com/legendapp/legend-state/llms.txt
A conditional rendering component that renders children only when a predicate is truthy. It re-evaluates independently without causing the parent to re-render. It supports `if`, `else`, and `ifReady` props.
```APIDOC
## `` — Conditional rendering without parent re-renders
Renders children only when a predicate is truthy. Re-evaluates independently without causing the parent to re-render.
```tsx
import { observable } from '@legendapp/state';
import { Show } from '@legendapp/state/react';
const isLoggedIn$ = observable(false);
const user$ = observable<{ name: string } | null>(null);
function App() {
return (
isLoggedIn$.set(true)}>Log in}
>
{/* Function child receives the value */}
{() =>
Welcome, {user$.name.get()}!
}
);
}
// ifReady — only shows when value is a non-empty object/array
const items$ = observable([]);
function ItemList() {
return (
Loading...}>
{() => items$.get().map(i =>