Skip to content

Reference-stable snapshots + useSyncExternalStore for React subscriptions #330

Description

@spencerc99

Context

#284 introduced an internal usePlayhtmlSubscription primitive in @playhtml/react (packages/react/src/hooks.ts) that backs usePresence, usePageData, usePresenceRoom, useUsers, and useCursorPresences. It hand-implements the subscribe/snapshot/lifecycle pattern that React 18 ships natively as useSyncExternalStore.

We can't adopt useSyncExternalStore today because it requires getSnapshot() to return a reference-stable value until the underlying data changes. playhtml's vanilla getters allocate on every call — users.getAll() builds a fresh array, presence.getPresences() builds a fresh Map — which makes useSyncExternalStore loop.

Proposal

  1. Add snapshot caching to the vanilla layer: getAll() / getPresences() / getCursorPresences() return the same reference until a change invalidates it. This is a standalone win — less allocation churn per read, and stable references let vanilla consumers use identity checks.
  2. Replace usePlayhtmlSubscription internals with useSyncExternalStore(subscribe, getSnapshot), shrinking the React layer to thin wrappers over a browser-standard primitive.

Constraint to preserve: every playhtml subscribe surface follows one convention — takes a callback, replays current state on subscribe, returns an unsubscribe function. That consistency is what makes both the vanilla usage pattern and the React fold-up work; new APIs should keep it.

Notes

  • Pairs naturally with the transport consolidation work (simplification audit Bucket 3) since presence snapshot construction is being reworked there anyway.
  • Care needed around cache invalidation and tearing semantics; this is subtle enough to deserve its own PR with tests, not a ride-along.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions