Skip to content

Add @solana/react/swr subpath with useRequestSWR#1713

Open
mcintyre94 wants to merge 1 commit into
react/use-tracked-datafrom
react/use-request-swr
Open

Add @solana/react/swr subpath with useRequestSWR#1713
mcintyre94 wants to merge 1 commit into
react/use-tracked-datafrom
react/use-request-swr

Conversation

@mcintyre94

@mcintyre94 mcintyre94 commented May 30, 2026

Copy link
Copy Markdown
Member

Summary of Changes

This PR adds the @solana/react/swr subpath, with an optional swr dependency.

We add the first hook to this subpath, useRequestSWR. This mirrors useRequest but provides the shape of useSWR:

function LatestBlockhash() {
    const client = useClient<ClientWithRpc<GetLatestBlockhashApi>>();
    const { data, error, isLoading, mutate } = useRequestSWR(['latestBlockhash'], client.rpc.getLatestBlockhash());
    if (error) return <button onClick={() => mutate()}>Retry</button>;
    if (isLoading) return <p>Loading…</p>;
    return <p>Blockhash: {data!.value.blockhash}</p>;
}

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 the getAbortSignal(): AbortSignal factory supported by useRequest. 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 useRequest it can take either a ReactiveActionSource such as PendingRpcRequest, or an async function (signal: AbortSignal) => Promise<T>. The function version is included for symmetry with useRequest, but other than when used with getAbortSignal is identical to (and is interoperable with) plain useSWR.

Like useRequest it 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 useActionSWR hook. This would provide nothing over useSWRMutation and I think it'd be confusing.

Following PRs will add SWR versions of useSubscription and useTrackedData.

@changeset-bot

changeset-bot Bot commented May 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 34a17a4

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

This PR includes changesets to release 47 packages
Name Type
@solana/react Major
@solana/accounts Major
@solana/addresses Major
@solana/assertions Major
@solana/codecs-core Major
@solana/codecs-data-structures Major
@solana/codecs-numbers Major
@solana/codecs-strings Major
@solana/codecs Major
@solana/compat Major
@solana/errors Major
@solana/fast-stable-stringify Major
@solana/fixed-points Major
@solana/functional Major
@solana/instruction-plans Major
@solana/instructions Major
@solana/keys Major
@solana/kit Major
@solana/nominal-types Major
@solana/offchain-messages Major
@solana/options Major
@solana/plugin-core Major
@solana/plugin-interfaces Major
@solana/program-client-core Major
@solana/programs Major
@solana/promises Major
@solana/rpc-api Major
@solana/rpc-graphql Major
@solana/rpc-parsed-types Major
@solana/rpc-spec-types Major
@solana/rpc-spec Major
@solana/rpc-subscriptions-api Major
@solana/rpc-subscriptions-channel-websocket Major
@solana/rpc-subscriptions-spec Major
@solana/rpc-subscriptions Major
@solana/rpc-transformers Major
@solana/rpc-transport-http Major
@solana/rpc-types Major
@solana/rpc Major
@solana/signers Major
@solana/subscribable Major
@solana/sysvars Major
@solana/transaction-confirmation Major
@solana/transaction-messages Major
@solana/transactions Major
@solana/wallet-account-signer Major
@solana/webcrypto-ed25519-polyfill Major

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

mcintyre94 commented May 30, 2026

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@mcintyre94

Copy link
Copy Markdown
Member Author

@trevor-cortex

@bundlemon

bundlemon Bot commented May 30, 2026

Copy link
Copy Markdown

BundleMon

Files updated (25)
Status Path Size Limits
react/dist/index.browser.mjs
5.06KB (+1.96KB +63.36%) -
react/dist/index.native.mjs
5.05KB (+1.96KB +63.38%) -
react/dist/index.node.mjs
5.05KB (+1.96KB +63.4%) -
@solana/kit production bundle
kit/dist/index.production.min.js
53.03KB (+429B +0.8%) -
kit/dist/index.browser.mjs
4.81KB (+349B +7.62%) -
kit/dist/index.native.mjs
4.81KB (+349B +7.63%) -
kit/dist/index.node.mjs
4.81KB (+349B +7.63%) -
errors/dist/index.browser.mjs
20.9KB (+179B +0.84%) -
errors/dist/index.native.mjs
20.9KB (+179B +0.84%) -
errors/dist/index.node.mjs
20.92KB (+179B +0.84%) -
wallet-account-signer/dist/index.browser.mjs
17.69KB (+169B +0.94%) -
wallet-account-signer/dist/index.native.mjs
17.69KB (+168B +0.94%) -
wallet-account-signer/dist/index.node.mjs
17.71KB (+168B +0.93%) -
subscribable/dist/index.browser.mjs
2.76KB (+160B +6%) -
subscribable/dist/index.native.mjs
2.77KB (+160B +5.99%) -
subscribable/dist/index.node.mjs
2.83KB (+158B +5.76%) -
rpc-types/dist/index.browser.mjs
1.9KB (+102B +5.53%) -
rpc-types/dist/index.native.mjs
1.9KB (+101B +5.48%) -
rpc-types/dist/index.node.mjs
1.9KB (+101B +5.48%) -
rpc-subscriptions-spec/dist/index.browser.mjs
2.19KB (-13B -0.58%) -
rpc-subscriptions-spec/dist/index.native.mjs
2.19KB (-13B -0.58%) -
rpc-subscriptions-spec/dist/index.node.mjs
2.23KB (-13B -0.56%) -
rpc-spec/dist/index.browser.mjs
898B (-20B -2.18%) -
rpc-spec/dist/index.native.mjs
897B (-21B -2.29%) -
rpc-spec/dist/index.node.mjs
896B (-21B -2.29%) -
Unchanged files (122)
Status Path Size Limits
rpc-graphql/dist/index.browser.mjs
18.82KB -
rpc-graphql/dist/index.native.mjs
18.81KB -
rpc-graphql/dist/index.node.mjs
18.81KB -
transaction-messages/dist/index.browser.mjs
11.32KB -
transaction-messages/dist/index.native.mjs
11.32KB -
transaction-messages/dist/index.node.mjs
11.32KB -
instruction-plans/dist/index.browser.mjs
6.58KB -
instruction-plans/dist/index.native.mjs
6.58KB -
instruction-plans/dist/index.node.mjs
6.58KB -
fixed-points/dist/index.browser.mjs
5.08KB -
fixed-points/dist/index.native.mjs
5.07KB -
fixed-points/dist/index.node.mjs
5.07KB -
codecs-data-structures/dist/index.browser.mjs
5.04KB -
codecs-data-structures/dist/index.native.mjs
5.03KB -
codecs-data-structures/dist/index.node.mjs
5.03KB -
offchain-messages/dist/index.browser.mjs
4.89KB -
offchain-messages/dist/index.native.mjs
4.89KB -
offchain-messages/dist/index.node.mjs
4.89KB -
transactions/dist/index.browser.mjs
4.07KB -
transactions/dist/index.native.mjs
4.07KB -
transactions/dist/index.node.mjs
4.07KB -
codecs-core/dist/index.browser.mjs
3.62KB -
codecs-core/dist/index.native.mjs
3.62KB -
codecs-core/dist/index.node.mjs
3.62KB -
webcrypto-ed25519-polyfill/dist/index.node.mj
s
3.61KB -
webcrypto-ed25519-polyfill/dist/index.browser
.mjs
3.59KB -
webcrypto-ed25519-polyfill/dist/index.native.
mjs
3.57KB -
rpc-subscriptions/dist/index.browser.mjs
3.37KB -
rpc-subscriptions/dist/index.node.mjs
3.34KB -
rpc-subscriptions/dist/index.native.mjs
3.31KB -
signers/dist/index.browser.mjs
3.26KB -
signers/dist/index.native.mjs
3.26KB -
signers/dist/index.node.mjs
3.26KB -
rpc-transformers/dist/index.browser.mjs
3.16KB -
rpc-transformers/dist/index.native.mjs
3.16KB -
rpc-transformers/dist/index.node.mjs
3.16KB -
keys/dist/index.node.mjs
3.06KB -
addresses/dist/index.browser.mjs
2.93KB -
addresses/dist/index.native.mjs
2.92KB -
addresses/dist/index.node.mjs
2.92KB -
keys/dist/index.browser.mjs
2.85KB -
keys/dist/index.native.mjs
2.85KB -
codecs-strings/dist/index.browser.mjs
2.55KB -
codecs-strings/dist/index.node.mjs
2.51KB -
codecs-strings/dist/index.native.mjs
2.47KB -
transaction-confirmation/dist/index.node.mjs
2.42KB -
transaction-confirmation/dist/index.native.mj
s
2.37KB -
sysvars/dist/index.browser.mjs
2.37KB -
sysvars/dist/index.native.mjs
2.37KB -
transaction-confirmation/dist/index.browser.m
js
2.37KB -
sysvars/dist/index.node.mjs
2.37KB -
rpc/dist/index.node.mjs
1.95KB -
codecs-numbers/dist/index.browser.mjs
1.95KB -
codecs-numbers/dist/index.native.mjs
1.95KB -
codecs-numbers/dist/index.node.mjs
1.94KB -
rpc-transport-http/dist/index.browser.mjs
1.89KB -
rpc-transport-http/dist/index.native.mjs
1.89KB -
rpc/dist/index.native.mjs
1.81KB -
rpc/dist/index.browser.mjs
1.8KB -
rpc-transport-http/dist/index.node.mjs
1.71KB -
rpc-subscriptions-channel-websocket/dist/inde
x.node.mjs
1.33KB -
rpc-subscriptions-channel-websocket/dist/inde
x.native.mjs
1.27KB -
rpc-subscriptions-channel-websocket/dist/inde
x.browser.mjs
1.26KB -
program-client-core/dist/index.browser.mjs
1.21KB -
program-client-core/dist/index.native.mjs
1.21KB -
program-client-core/dist/index.node.mjs
1.21KB -
options/dist/index.browser.mjs
1.18KB -
options/dist/index.native.mjs
1.18KB -
options/dist/index.node.mjs
1.17KB -
accounts/dist/index.browser.mjs
1.17KB -
accounts/dist/index.native.mjs
1.17KB -
accounts/dist/index.node.mjs
1.16KB -
rpc-api/dist/index.browser.mjs
998B -
rpc-api/dist/index.native.mjs
997B -
rpc-api/dist/index.node.mjs
995B -
compat/dist/index.browser.mjs
969B -
compat/dist/index.native.mjs
968B -
compat/dist/index.node.mjs
966B -
rpc-spec-types/dist/index.browser.mjs
962B -
rpc-spec-types/dist/index.native.mjs
961B -
rpc-spec-types/dist/index.node.mjs
959B -
rpc-subscriptions-api/dist/index.native.mjs
871B -
rpc-subscriptions-api/dist/index.browser.mjs
870B -
rpc-subscriptions-api/dist/index.node.mjs
870B -
promises/dist/index.native.mjs
841B -
promises/dist/index.node.mjs
840B -
promises/dist/index.browser.mjs
839B -
plugin-core/dist/index.browser.mjs
820B -
plugin-core/dist/index.native.mjs
819B -
plugin-core/dist/index.node.mjs
817B -
assertions/dist/index.browser.mjs
783B -
instructions/dist/index.browser.mjs
771B -
instructions/dist/index.native.mjs
770B -
instructions/dist/index.node.mjs
768B -
fast-stable-stringify/dist/index.browser.mjs
726B -
fast-stable-stringify/dist/index.native.mjs
725B -
assertions/dist/index.native.mjs
724B -
fast-stable-stringify/dist/index.node.mjs
724B -
assertions/dist/index.node.mjs
723B -
programs/dist/index.browser.mjs
329B -
programs/dist/index.native.mjs
327B -
programs/dist/index.node.mjs
325B -
fs-impl/dist/index.browser.mjs
245B -
event-target-impl/dist/index.node.mjs
230B -
functional/dist/index.browser.mjs
154B -
functional/dist/index.native.mjs
152B -
text-encoding-impl/dist/index.native.mjs
152B -
functional/dist/index.node.mjs
151B -
codecs/dist/index.browser.mjs
145B -
codecs/dist/index.native.mjs
144B -
codecs/dist/index.node.mjs
142B -
event-target-impl/dist/index.browser.mjs
133B -
ws-impl/dist/index.node.mjs
131B -
text-encoding-impl/dist/index.browser.mjs
122B -
fs-impl/dist/index.node.mjs
120B -
text-encoding-impl/dist/index.node.mjs
119B -
ws-impl/dist/index.browser.mjs
113B -
crypto-impl/dist/index.node.mjs
111B -
crypto-impl/dist/index.browser.mjs
109B -
rpc-parsed-types/dist/index.browser.mjs
66B -
rpc-parsed-types/dist/index.native.mjs
65B -
rpc-parsed-types/dist/index.node.mjs
63B -

Total files change +9.01KB +1.71%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@trevor-cortex trevor-cortex 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.

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 == null forces key to null at the SWR call site, but the fetcher also has a defensive current == null → return undefined as T branch. This is a deliberate belt-and-braces guard against a race where SWR schedules a fetch with a non-null key and then source flips to null before the fetcher body runs. The cost is that undefined ends 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 to null and SWR stops surfacing it, but it's worth being aware of if you ever debug a weird data === undefined after a transition. I don't think it needs to change; just noting it for future readers.
  • A mutate() racing with another mutate() (or a focus/poll revalidation) starts a new store per fetch and doesn't reset() the prior one. SWR's model is to discard the stale result, not cancel the network call, and the README calls this out explicitly. With getAbortSignal configured 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 once useSubscriptionSWR / useTrackedDataSWR follow — the long-lived subscription case will need a different story.
  • UseRequestOptions is imported as a type from ../useRequest — fine because it's import type so it's erased at compile time, but worth a sanity check that the produced dist/swr.*.mjs doesn't accidentally pull useRequest's runtime in. Tree-shake should handle it, but there's no test:treeshakability:* script wired up for the swr.* outputs yet.
  • The generics on useRequestSWR<T, TError = Error> require an explicit T when source is null (no value to infer from). The type test documents this with the useRequestSWR<{ slot: bigint }>(['epochInfo'], null) case. It's the right tradeoff but worth surfacing in the README example for the null-source pattern, since useRequest doesn't have this constraint.

Notes for subsequent reviewers

  • Verify the package.json exports map 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-level browser field also maps the new swr.node.* files to swr.browser.*. Worth a quick node -e "require.resolve('@solana/react/swr')" from a few sample environments once published, or at least eyeballing against the existing @solana/kit/program-client-core shape.
  • The build-script change in getBaseConfig.ts extends 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 because act awaits the microtask queue; no need to reach for fake timers here.
  • No changes to useRequest itself — pure additive PR. Existing useRequest consumers are unaffected.

@github-actions

github-actions Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Documentation Preview: https://kit-docs-p1ri5o3r2-anza-tech.vercel.app

@mcintyre94 mcintyre94 force-pushed the react/use-request-swr branch from ad0cadc to 25d40de Compare June 3, 2026 09:00
@mcintyre94

Copy link
Copy Markdown
Member Author

A mutate() racing with another mutate() (or a focus/poll revalidation) starts a new store per fetch and doesn't reset() the prior one. SWR's model is to discard the stale result, not cancel the network call [...] worth keeping in mind once useSubscriptionSWR / useTrackedDataSWR follow — the long-lived subscription case will need a different story.

Agreed. We will use useSWRSubscription internally for the subscription/trackedData SWR hooks which will handle cleanup. This will be less clean for tanstack unfortunately since it doesn't have an equivalent, we'll need a useEffect to handle it. Not an issue for this PR though.

UseRequestOptions is imported as a type from ../useRequest [...] Tree-shake should handle it, but there's no test:treeshakability:* script wired up for the swr.* outputs yet.

Updated the test:treeshakability scripts for react to also check swr. Will add tanstack query later too.

The generics on useRequestSWR<T, TError = Error> require an explicit T when source is null (no value to infer from). The type test documents this with the useRequestSWR<{ slot: bigint }>(['epochInfo'], null) case. It's the right tradeoff but worth surfacing in the README example for the null-source pattern, since useRequest doesn't have this constraint.

I think this is a misunderstanding, useRequest is typed the same way, and also requires an explicit T when the source is null if you want it typed.

@mcintyre94 mcintyre94 marked this pull request as ready for review June 3, 2026 09:01
@mcintyre94 mcintyre94 requested a review from lorisleiva June 3, 2026 09:02
@mcintyre94 mcintyre94 force-pushed the react/use-request-swr branch from 25d40de to 773481f Compare June 3, 2026 09:14
@mcintyre94 mcintyre94 force-pushed the react/use-tracked-data branch from 6e6bf87 to e49766d Compare June 3, 2026 15:17
@mcintyre94 mcintyre94 force-pushed the react/use-request-swr branch from 773481f to 5d60581 Compare June 3, 2026 15:17
@mcintyre94 mcintyre94 force-pushed the react/use-tracked-data branch from e49766d to dc23acc Compare June 5, 2026 15:39
@mcintyre94 mcintyre94 force-pushed the react/use-request-swr branch from 5d60581 to d78c67a Compare June 5, 2026 15:39
@mcintyre94 mcintyre94 force-pushed the react/use-request-swr branch from d78c67a to b30ff51 Compare June 5, 2026 15:47
@mcintyre94 mcintyre94 force-pushed the react/use-tracked-data branch 2 times, most recently from d3539dc to 4815bc9 Compare June 5, 2026 15:56
@mcintyre94 mcintyre94 force-pushed the react/use-request-swr branch from b30ff51 to 1e1ddab Compare June 5, 2026 15:56
@socket-security

socket-security Bot commented Jun 5, 2026

Copy link
Copy Markdown

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

@mcintyre94 mcintyre94 force-pushed the react/use-request-swr branch from 1e1ddab to 16debfb Compare June 5, 2026 17:48

@lorisleiva lorisleiva left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you! 🫶

@mcintyre94 mcintyre94 force-pushed the react/use-tracked-data branch from 4815bc9 to b853254 Compare June 10, 2026 15:43
@mcintyre94 mcintyre94 force-pushed the react/use-request-swr branch from 16debfb to 1dc93f1 Compare June 10, 2026 15:43
@mcintyre94 mcintyre94 force-pushed the react/use-tracked-data branch from b853254 to 711d36e Compare June 10, 2026 16:49
@mcintyre94 mcintyre94 force-pushed the react/use-request-swr branch from 1dc93f1 to 6a213e8 Compare June 10, 2026 16:49
@mcintyre94 mcintyre94 added the do-not-close Add this tag to exempt an issue/PR from being closed by the stalebot label Jun 11, 2026
@mcintyre94 mcintyre94 force-pushed the react/use-tracked-data branch from 711d36e to 5348ef5 Compare June 12, 2026 17:53
@mcintyre94 mcintyre94 force-pushed the react/use-request-swr branch from 6a213e8 to 51dc371 Compare June 12, 2026 17:53
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-close Add this tag to exempt an issue/PR from being closed by the stalebot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants