Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,36 @@ import { ObservablePrimitiveClass } from './ObservablePrimitive';
import { createObservable } from './createObservable';
import type { Observable, ObservablePrimitive, RecursiveValueOrFunction } from './observableTypes';

/**
* Create an observable from an initial value.
*
* **Important:** when `value` is a plain object or array, it is stored **by reference** and
* mutated in place as fields are updated via `.set()`. After the first child set, the
* variable you passed in no longer holds the original state — it holds the current state.
*
* This is intentional (it's how Legend State avoids cloning on every update), but it
* surprises people coming from Zustand/Redux/MobX. The most common gotcha is using a
* shared `initialState` constant as a "reset target":
*
* ```ts
* const initialState = { count: 0 };
* const store$ = observable(initialState);
*
* store$.count.set(5);
* console.log(initialState); // { count: 5 } ← mutated in place
*
* store$.set(initialState); // ❌ no-op: structurally equal to current value
* ```
*
* If you want a stable "reset target", pass a fresh object/literal each time — typically
* via a factory:
*
* ```ts
* const createInitialState = () => ({ count: 0 });
* const store$ = observable(createInitialState());
* store$.set(createInitialState()); // ✅ fresh object, set fires
* ```
*/
export function observable<T>(): Observable<T | undefined>;
export function observable<T>(
value: Promise<RecursiveValueOrFunction<T>> | (() => RecursiveValueOrFunction<T>) | RecursiveValueOrFunction<T>,
Expand Down