Skip to content

State Store

Each actor handle has an internal StateStore that manages the client-side copy of the actor’s state.

  1. When you call handle.state.subscribe(), the client sends a state:sub message
  2. The server responds with a state:snapshot containing the full state
  3. On each state change, the server sends state:patch with JSON Patch operations
  4. The StateStore applies patches to its local copy and notifies subscribers
class StateStore<TState> {
getState(): TState | undefined;
setSnapshot(state: TState): void;
applyPatches(patches: JsonPatchOp[]): void;
subscribe(listener: (state: TState) => void): Unsubscribe;
get subscriberCount(): number;
}

You typically interact with the store through the handle’s .state property, not directly.

The store implements a subset of RFC 6902:

OperationDescription
addAdd a value at a path (including array append with -)
replaceReplace the value at a path
removeRemove the value at a path

Paths follow JSON Pointer syntax (RFC 6901):

/messages/0/text → state.messages[0].text
/players/- → append to state.players array
/count → state.count

Special characters are escaped: ~0 for ~, ~1 for /.

Patches are applied using structuredClone() — each update produces a new state object. This ensures React’s useSyncExternalStore detects changes correctly.

Subscribers are called synchronously after each state update (snapshot or patch). If state is undefined (no snapshot received yet), subscribers are not called.

When a new subscriber is added and state is already available, the subscriber is called immediately with the current state.

The useActorState hook from @zocket/react is built on top of StateStore:

// Subscribes to the store, re-renders on changes
const messages = useActorState(room, (s) => s.messages);

See React Hooks for details on selectors and memoization.