Add @solana/react/swr subpath with useRequestSWR#1713
Conversation
🦋 Changeset detectedLatest commit: 34a17a4 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 ➡️ |
trevor-cortex
left a comment
There was a problem hiding this comment.
Summary
Adds a new @solana/react/swr subpath export with useRequestSWR(key, source, options?) — the SWR-backed counterpart to useRequest. Same source shape (ReactiveActionSource<T> or (signal: AbortSignal) => Promise<T>), returns SWR's native SWRResponse<T>. swr@^2 is added as an optional peer dependency, and the build is taught to emit a second entry per platform (dist/swr.<platform>.<format>). The changeset is a minor bump on @solana/react and matches the API surface added.
Overall this is a clean, well-scoped first hook for the adapter. The implementation closely mirrors useRequest (same ref-sync pattern for getAbortSignal, same source-vs-function dispatch fork), and the test file is thorough — both source shapes covered, abort wiring asserted, null-key/null-source disable transitions exercised, and there's even an SSR smoke test confirming the fetcher doesn't fire on the server. The README and docblock are excellent: the explicit note about SWR's discard-vs-cancel model and the mutate()-vs-refresh({ abortSignal }) callout will save users real confusion.
Things to watch out for
source == nullforceskeytonullat the SWR call site, but the fetcher also has a defensivecurrent == null → return undefined as Tbranch. This is a deliberate belt-and-braces guard against a race where SWR schedules a fetch with a non-null key and thensourceflips to null before the fetcher body runs. The cost is thatundefinedends up in SWR's cache for that key as a successful value — which is fine in practice because the very next render forces the key tonulland SWR stops surfacing it, but it's worth being aware of if you ever debug a weirddata === undefinedafter a transition. I don't think it needs to change; just noting it for future readers.- A
mutate()racing with anothermutate()(or a focus/poll revalidation) starts a new store per fetch and doesn'treset()the prior one. SWR's model is to discard the stale result, not cancel the network call, and the README calls this out explicitly. WithgetAbortSignalconfigured the prior request can be cancelled via the user's own timeout, but without it the underlying RPC call keeps running until it naturally settles. Already documented; no action needed, just worth keeping in mind onceuseSubscriptionSWR/useTrackedDataSWRfollow — the long-lived subscription case will need a different story. UseRequestOptionsis imported as a type from../useRequest— fine because it'simport typeso it's erased at compile time, but worth a sanity check that the produceddist/swr.*.mjsdoesn't accidentally pulluseRequest's runtime in. Tree-shake should handle it, but there's notest:treeshakability:*script wired up for theswr.*outputs yet.- The generics on
useRequestSWR<T, TError = Error>require an explicitTwhensourceisnull(no value to infer from). The type test documents this with theuseRequestSWR<{ slot: bigint }>(['epochInfo'], null)case. It's the right tradeoff but worth surfacing in the README example for the null-source pattern, sinceuseRequestdoesn't have this constraint.
Notes for subsequent reviewers
- Verify the
package.jsonexportsmap shape resolves correctly for all the target runtimes — there are five condition keys per subpath (edge-light,workerd,browser,node,react-native) and the top-levelbrowserfield also maps the newswr.node.*files toswr.browser.*. Worth a quicknode -e "require.resolve('@solana/react/swr')"from a few sample environments once published, or at least eyeballing against the existing@solana/kit/program-client-coreshape. - The build-script change in
getBaseConfig.tsextends the existing per-package subpath logic with a nested ternary. Functionally fine, but if a third package ever gets subpath entries it'll want to be refactored into a small map. - The test file leans on
await act(async () => resolve(value))to flush SWR's effect-driven fetch. This matches the project's preference for end-state assertions under StrictMode and works becauseactawaits the microtask queue; no need to reach for fake timers here. - No changes to
useRequestitself — pure additive PR. ExistinguseRequestconsumers are unaffected.
|
Documentation Preview: https://kit-docs-p1ri5o3r2-anza-tech.vercel.app |
ad0cadc to
25d40de
Compare
Agreed. We will use
Updated the
I think this is a misunderstanding, |
25d40de to
773481f
Compare
6e6bf87 to
e49766d
Compare
773481f to
5d60581
Compare
e49766d to
dc23acc
Compare
5d60581 to
d78c67a
Compare
d78c67a to
b30ff51
Compare
d3539dc to
4815bc9
Compare
b30ff51 to
1e1ddab
Compare
|
No dependency changes detected. Learn more about Socket for GitHub. 👍 No dependency changes detected in pull request |
1e1ddab to
16debfb
Compare
4815bc9 to
b853254
Compare
16debfb to
1dc93f1
Compare
b853254 to
711d36e
Compare
1dc93f1 to
6a213e8
Compare
711d36e to
5348ef5
Compare
6a213e8 to
51dc371
Compare
Adds an opt-in `@solana/react/swr` subpath that bridges Kit's reactive primitives into SWR's cache. Hooks under this adapter carry the `SWR` suffix to keep the cache backing visible at the call site, mirroring the spec's naming convention. `useRequestSWR(key, source, options?)` is the SWR-backed counterpart to `useRequest`. Accepts the same source shape — a `ReactiveActionSource<T>` (satisfied by `PendingRpcRequest<T>`) or a `(signal: AbortSignal) => Promise<T>` function — and routes it through SWR so components reading the same key share one in-flight fetch and participate in SWR's revalidation, persistence, devtools, and Suspense semantics. Returns SWR's native `SWRResponse<T>` (not a Kit-flavored shape) so SWR-savvy users see the API they expect. Either `key === null` or `source === null` disables the fetch. The hook coerces key to null when source is null so the two halves can be gated independently — useful when one of the source's inputs isn't yet known. Mirrors `useRequest`'s nullable-source pattern. Options merge SWR's `SWRConfiguration` with the Kit-only `getAbortSignal: () => AbortSignal` option imported from `useRequest` — same factory pattern. Invoked per attempt; the returned signal is threaded into the source (`fn(signal)` for function sources, `withSignal(signal).dispatchAsync()` for `ReactiveActionSource`). Primary use is per-attempt timeouts. SWR doesn't know about the abort itself — only the source does — but the source's rejection surfaces via `result.error`. To re-fire on demand, use SWR's native `result.mutate()` (no Kit-specific `refresh` is exposed; SWR owns that verb). Subpath plumbing follows the existing `@solana/kit/program-client-core` pattern: the `getBaseConfig.ts` tsup helper special-cases `@solana/react` to emit a `swr.*` bundle per platform, `tsconfig.declarations.json` includes the new entry, and `package.json` exports the subpath alongside the main entry with a parallel browser remap. `swr@^2` is an optional peer dependency — consumers who don't use the subpath aren't required to install it.
5348ef5 to
78029ec
Compare
51dc371 to
34a17a4
Compare

Summary of Changes
This PR adds the
@solana/react/swrsubpath, with an optionalswrdependency.We add the first hook to this subpath,
useRequestSWR. This mirrorsuseRequestbut provides the shape ofuseSWR:This enables all the niceties of SWR: shared cache (based on key) across components, dedupe, automatic refetching, global config, etc
A third optional param allows passing any SWR configuration (same as
useSWR), as well as thegetAbortSignal(): AbortSignalfactory supported byuseRequest. While SWR doesn't have specific abort signal handling, the signal is passed to each fetch. Note that SWR config passed will be merged (by SWR) with any global config in the normal way.Like
useRequestit can take either aReactiveActionSourcesuch asPendingRpcRequest, or an async function(signal: AbortSignal) => Promise<T>. The function version is included for symmetry withuseRequest, but other than when used withgetAbortSignalis identical to (and is interoperable with) plainuseSWR.Like
useRequestit does not fetch when the source is null. SWR disables automatically if the key is null, so we disable by setting the key to null. This removes the need to test against null too.I've included a note in the readme that we will not have a
useActionSWRhook. This would provide nothing overuseSWRMutationand I think it'd be confusing.Following PRs will add SWR versions of
useSubscriptionanduseTrackedData.