Add useSubscriptionSWR hook#1719
Conversation
🦋 Changeset detectedLatest commit: b1c4037 The changes in this PR will be included in the next version bump. This PR includes changesets to release 47 packages
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 |
BundleMonFiles updated (25)
Unchanged files (122)
Total files change +9.01KB +1.71% Final result: ✅ View report in BundleMon website ➡️ |
|
Documentation Preview: https://kit-docs-945nn6vhe-anza-tech.vercel.app |
trevor-cortex
left a comment
There was a problem hiding this comment.
Summary
Adds useSubscriptionSWR — the SWR-backed counterpart to useSubscription, mirroring the shape and conventions of useRequestSWR. It wraps useSWRSubscription, accepts a ReactiveStreamSource<T> and SWR key (either nullable to disable), splits off the Kit-specific getAbortSignal factory from the rest of the SWR config, ref-syncs source/factory so inline values don't churn the subscribe callback's identity, and tears down the underlying store on cleanup via unsubscribe() + store.reset(). Multiple components with the same key share one underlying subscription (SWR dedupes), and either-null gating works by forcing the SWR key to null when source is null. Ships with a thorough browser test suite (envelope vs raw notifications, error channel, both-null gating, transitions in/out of null, key changes, dedup, abort-signal forwarding, SSR), a typetest covering envelope/raw shapes, default-unknown error type and overrides, options forwarding, and a minor changeset.
Things to watch out for
- No
reconnect()affordance.useSubscriptionexposes areconnect()callback as part of its result, butSWRSubscriptionResponsedoesn't have an equivalent and there's no way to forceuseSWRSubscriptionto re-open a stream while the key is stable (SWR'smutatedoesn't reopen subscriptions). In practice the only way to reconnect is to change the SWR key. This is an inherent limitation ofuseSWRSubscription, not a bug, but it's a meaningful semantic difference fromuseSubscriptionthat the README doesn't currently call out — worth a sentence so users picking between the two hooks aren't surprised. - "Per-connection signals" wording in the README is slightly off. Because there's no
reconnect(),getAbortSignalis invoked once persubscribeinvocation (i.e. once per unique key, deduped across components), not once per connection in theuseSubscriptionsense. Minor doc nit. - Bump level.
minorfor a new public hook is correct per the repo'sCLAUDE.md(pre-1.0, new feature).
Notes for subsequent reviewers
- The
subscribecallback subscribes to the store before callingconnect(), which is the right order to avoid missing synchronous state transitions. The listener only forwardsloaded/errorstates tonext;loading/idleare correctly ignored so SWR'sdataretains its prior value across reconnect-style transitions (stale-while-revalidate is preserved at the kit-store layer, even thoughuseSWRSubscriptionitself doesn't have that concept). - The shared-subscription test (
'shares one active underlying subscription across components with the same key') relies on SWR's per-key dedup: only onesubscribeinvocation fires per key, sosourceRef.currentfrom whichever render wins is what gets used. Worth being aware of if two callers ever pass different sources under the same key — the later mount won't re-subscribe. - The
useCallback(subscribe, [])+ ref-sync pattern matchesuseRequestSWRexactly. Good consistency. - Cleanup calls both
unsubscribe()(removes our listener) andstore.reset()(aborts the connection). Since the store is created locally persubscribeinvocation and has no other listeners, this is the correct teardown. - The abort-signal test is genuinely exercising the kit store's abort handling (not the fake publisher's listener removal) — the store transitions to
errorwith the abort reason, which the subscribe listener then forwards vianext(reason). Good coverage.
| const getAbortSignalRef = useRef(getAbortSignal); | ||
| useIsomorphicLayoutEffect(() => { | ||
| sourceRef.current = source; | ||
| getAbortSignalRef.current = getAbortSignal; | ||
| }); |
There was a problem hiding this comment.
Optional: consider seeding from store.getUnifiedState() immediately after subscribing, in case the store is ever in a non-idle state at the moment we attach (e.g. a future change where stores can be pre-warmed via a cache). Today every source.reactiveStore() returns an idle store so this is a no-op, but it would make the bridge resilient to that assumption changing. Not blocking.
There was a problem hiding this comment.
Agree with this, and I think this is quite important because a store defined by a plugin can have whatever behaviour that plugin wants. This makes the hook a bit more complex, but is worth it IMO.
Note that other hooks don't have this issue because useSyncExternalStore handles the initial state.
There was a problem hiding this comment.
Actually this is wrong and I've reverted the change. .reactiveStore() is documented as returning an idle store, and this is an important part of the design that we settled on. So useSubscriptionSWR can trust the state to start idle and only change after it has subscribed and connected.
773481f to
5d60581
Compare
3b9a423 to
f3f39e1
Compare
f3f39e1 to
7f08fc4
Compare
5d60581 to
d78c67a
Compare
7f08fc4 to
948efe9
Compare
d78c67a to
b30ff51
Compare
603b8f5 to
783f65e
Compare
783f65e to
f835130
Compare
1e1ddab to
16debfb
Compare
f835130 to
378f178
Compare
378f178 to
7491dbe
Compare
16debfb to
1dc93f1
Compare
7491dbe to
de50afe
Compare
1dc93f1 to
6a213e8
Compare
de50afe to
d07c735
Compare
Add an SWR-backed subscription hook for ReactiveStreamSource values on the @solana/react/swr subpath. The hook shares subscriptions by SWR key, returns SWR's native data/error shape, supports nullable key or source gating, and threads Kit's getAbortSignal option into each connection. Document the new hook, export it from the SWR entrypoint, and cover notification passthrough, error handling, disabled states, key/source lifecycle behavior, shared subscriptions, StrictMode rendering, SSR safety, and public typing.
d07c735 to
b1c4037
Compare
51dc371 to
34a17a4
Compare

Summary of Changes
This PR adds
useSubscriptionSWR, the SWR shaped version ofuseSubscription.Similarly to
useRequestSWRthe third param is anSWRConfiguration, with our usualgetAbortSignalfactory to add a custom signal. It also disables when either key or source are null. Note that SWR subscriptions don't include thereconnectfunction fromuseSubscription.Note that under the hood this uses
useSWRSubscription, which handles cleaning up the subscription and resetting the store.