Skip to content

feat(graphql-react-ws): syncStore opt-out for callback-only subscriptions - #180

Merged
jswirbs merged 3 commits into
mainfrom
justin/sync-store-opt-out
Jul 31, 2026
Merged

feat(graphql-react-ws): syncStore opt-out for callback-only subscriptions#180
jswirbs merged 3 commits into
mainfrom
justin/sync-store-opt-out

Conversation

@jswirbs

@jswirbs jswirbs commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds syncStore to useSubscription (default true, backwards compatible).
  • When syncStore: false, results are delivered only via onData/onError and are not mirrored into the valtio store — so useSnapshot does not re-render the calling component on every frame.
  • Motivated by high-frequency GraphQL subscriptions (e.g. Vault chat reactions / typing digests) that write into an external cache and never read returned data/error.

Why

Today every subscription frame does store.data = … + useProxySnapshot(store), which forces a React re-render of whatever component called the hook — even if the app only uses onData. Under chat fan-out that re-renders entire channel shells.

App-side workarounds (null-rendering leaf components) work but push the cost of a library default onto every consumer. This flag fixes it at the source.

API

useSubscription({
  query,
  variables,
  onData,
  onError,
  /** @default true */
  syncStore: false, // callback-only; no per-frame re-render
});

Returned data/error do not update while syncStore is false.

Test plan

  • Unit tests: onData still fires with syncStore: false
  • Unit tests: caller render count does not increase across multiple frames when syncStore: false
  • Unit tests: default syncStore: true still updates returned data
  • Unit tests: fatal onError still fires with syncStore: false
  • After publish (@soundxyz/graphql-react-ws patch), Vault can pass syncStore: false from useResilientSubscription and optionally remove the *Subscriber leaf components

Deployment considerations

  • Patch release via changesets.
  • Consumers must bump @soundxyz/graphql-react-ws and opt in with syncStore: false to benefit — no behavior change at default.

Other call outs

N/A

Made with Cursor

…riptions

High-frequency consumers that only use onData/onError (e.g. chat cache
writers) were forced to re-render on every frame because useSubscription
always mirrored results into a valtio store read via useSnapshot. syncStore:
false skips those writes while keeping callbacks and default reactive
behavior unchanged.
@changeset-bot

changeset-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f361306

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@soundxyz/graphql-react-ws Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@jswirbs

jswirbs commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor

cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown

Skipping Bugbot: Bugbot is disabled for this repository. Visit the Bugbot dashboard to update your settings.

@jswirbs

jswirbs commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5fe13f6. Configure here.

Comment thread packages/graphql-react-ws/src/client.ts
…is false

Callback-only listeners share the valtio store and the same broadcast result
object with syncStore:true peers. Updating store.ref.current from a
syncStore:false listener could mark the result as applied before a reactive
peer wrote data/error, leaving returned values stale. Only syncStore:true
consumers participate in the shared-store dedup now.
@jswirbs

jswirbs commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Re: Bugbot shared-store skip via ref — confirmed real, fixed in 63cf309.

Same query+variables share one store, and the broadcaster hands every listener the same result object. A syncStore: false effect was still doing store.ref.current = result, so if it ran first a syncStore: true peer hit store.ref.current !== result as false and skipped the data/error write.

Fix: only advance store.ref when syncStore is true. Added a regression test that mounts the callback-only listener first.

…e snapshot

syncStore:false still called useProxySnapshot on the shared store and could
write initialData into it, so a reactive peer's store write re-rendered the
callback-only mount and contradicted the no-re-render guarantee. Snapshot an
inert proxy instead, skip initialData store writes, and return null data/error.
@jswirbs
jswirbs merged commit df90c58 into main Jul 31, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant