Skip to content

Add the RPC plugin to the react-app example - #1844

Merged
mcintyre94 merged 1 commit into
mainfrom
react-app-rpc-plugin
Aug 5, 2026
Merged

Add the RPC plugin to the react-app example#1844
mcintyre94 merged 1 commit into
mainfrom
react-app-rpc-plugin

Conversation

@mcintyre94

@mcintyre94 mcintyre94 commented Jul 14, 2026

Copy link
Copy Markdown
Member

This PR adds the solanaRpc plugin to the example app, and generalises its WalletClientProvider to just ClientProvider. It replaces use of rpc and rpcSubscriptions to come from the client, and removes the old RpcContextProvider that manually handled the same state.

It uses our newly documented pattern of useClient<AppClient>()

This PR does not yet use any of the other features of solanaRpc, such as the instruction plans plugin, but intentionally installs solanaRpc so that future PRs can.

An issue this caused is that the rpc now waits to update until the wallet is ready, since it updates with the client. This is I think correct - the balance to display depends on the connected wallet. Technically slot doesn't need to wait though. This means that the selected chain from ChainContext can be out of sync with the rpc. We use extendClient to add the chain to the client, and update callers that should depend on the active chain to read chain from the client. The select UI still reads the chain from the ChainContext so that it updates immediately.

This leads to 2 points:

  • We could consider adding a chain plugin, that exposes chain and setChain, and we could add a useChain react hook
  • If we did this, we'd need a concept of selected (updates immediately) and active (updates when the client is ready and updates). This might point to us needing a way to access the 'warming up' client immediately when we add ManagedClientProvider (Add ManagedClientProvider to @solana/react (manage runtime client swaps without UI flash) #1843) so that components can decide whether to render immediately or wait for the client to be ready.

@bundlemon

bundlemon Bot commented Jul 14, 2026

Copy link
Copy Markdown

BundleMon

Unchanged files (150)
Status Path Size Limits
@solana/kit production bundle
kit/dist/index.production.min.js
55.54KB -
errors/dist/index.node.mjs
21.6KB -
errors/dist/index.browser.mjs
21.58KB -
errors/dist/index.native.mjs
21.58KB -
rpc-graphql/dist/index.browser.mjs
18.82KB -
rpc-graphql/dist/index.native.mjs
18.82KB -
rpc-graphql/dist/index.node.mjs
18.82KB -
wallet-account-signer/dist/index.node.mjs
18.31KB -
wallet-account-signer/dist/index.browser.mjs
18.29KB -
wallet-account-signer/dist/index.native.mjs
18.29KB -
transaction-messages/dist/index.browser.mjs
11.34KB -
transaction-messages/dist/index.native.mjs
11.34KB -
transaction-messages/dist/index.node.mjs
11.34KB -
instruction-plans/dist/index.browser.mjs
7.02KB -
instruction-plans/dist/index.native.mjs
7.02KB -
instruction-plans/dist/index.node.mjs
7.02KB -
codecs-data-structures/dist/index.browser.mjs
5.3KB -
codecs-data-structures/dist/index.native.mjs
5.3KB -
codecs-data-structures/dist/index.node.mjs
5.29KB -
fixed-points/dist/index.browser.mjs
5.08KB -
fixed-points/dist/index.native.mjs
5.07KB -
fixed-points/dist/index.node.mjs
5.07KB -
offchain-messages/dist/index.browser.mjs
5.06KB -
offchain-messages/dist/index.native.mjs
5.06KB -
offchain-messages/dist/index.node.mjs
5.06KB -
react/dist/index.browser.mjs
5.02KB -
react/dist/index.node.mjs
5.02KB -
react/dist/index.native.mjs
5.02KB -
kit/dist/index.browser.mjs
4.61KB -
kit/dist/index.native.mjs
4.6KB -
kit/dist/index.node.mjs
4.6KB -
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.15KB -
subscribable/dist/index.node.mjs
3.13KB -
keys/dist/index.node.mjs
3.06KB -
subscribable/dist/index.native.mjs
3.06KB -
subscribable/dist/index.browser.mjs
3.05KB -
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 -
transaction-introspection/dist/index.browser.
mjs
2.73KB -
transaction-introspection/dist/index.native.m
js
2.73KB -
transaction-introspection/dist/index.node.mjs
2.73KB -
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-subscriptions-spec/dist/index.node.mjs
2.23KB -
rpc-subscriptions-spec/dist/index.native.mjs
2.19KB -
rpc-subscriptions-spec/dist/index.browser.mjs
2.19KB -
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-types/dist/index.browser.mjs
1.9KB -
rpc-types/dist/index.native.mjs
1.9KB -
rpc-types/dist/index.node.mjs
1.9KB -
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 -
plugin-core/dist/index.browser.mjs
1.18KB -
plugin-core/dist/index.native.mjs
1.18KB -
plugin-core/dist/index.node.mjs
1.18KB -
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-spec-types/dist/index.browser.mjs
1.15KB -
rpc-spec-types/dist/index.native.mjs
1.15KB -
rpc-spec-types/dist/index.node.mjs
1.15KB -
rpc-api/dist/index.browser.mjs
1.04KB -
rpc-api/dist/index.native.mjs
1.04KB -
rpc-api/dist/index.node.mjs
1.04KB -
compat/dist/index.browser.mjs
969B -
compat/dist/index.native.mjs
968B -
compat/dist/index.node.mjs
966B -
rpc-spec/dist/index.browser.mjs
898B -
rpc-spec/dist/index.native.mjs
897B -
rpc-spec/dist/index.node.mjs
896B -
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 -
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 -

No change in files bundle size

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@mcintyre94
mcintyre94 force-pushed the react-app-rpc-plugin branch from 260bb1e to 4b243e5 Compare July 14, 2026 17:31
@changeset-bot

changeset-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 1ba30bd

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@mcintyre94

Copy link
Copy Markdown
Member Author

@trevor-cortex

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

Solid refactor. The move to solanaRpc + extendClient({ chain }) collapses two overlapping context providers into one and delivers a nice property: the rpc and the chain a component reads are now guaranteed to be in lockstep, because both come off the same client reference. The Balance refetch test is a great codification of the invariant.

Main thing to look at

The two chain sources aren't consistently applied across components. The PR intentionally moves Balance and Root to read chain from the client (with excellent comments explaining why — active vs. selected), but the sibling components (AirdropButton, SolanaSignAndSendTransactionFeaturePanel, SolanaSignTransactionFeaturePanel, SolanaPartialSignTransactionFeaturePanel, SlotIndicator) silently keep reading chain / solanaExplorerClusterName from ChainContext. Some of these uses are genuinely fine — for example the account-filtering Selects want the eagerly-updated selection so the UI reflects the user's choice immediately, matching the same rationale as the chain switcher itself. Others are more subtle:

  • AirdropButton: isMainnet (from ChainContext) disables the button based on the selected chain, but the airdrop actually runs against client.rpc. In practice this fails safe (the mainnet rpc has no requestAirdrop, and the mainnet→devnet direction just hits a devnet rpc that supports it), but the eager/lagging split is worth being deliberate about.
  • solanaExplorerClusterName in SlotIndicator, AirdropButton, and the transaction panels updates eagerly on chain switch. During the brief warm-up window, a slot / signature that was actually fetched from the previous network can be rendered inside an explorer link pointing at the newly-selected network. It's a narrow window, but the whole point of the client-derived chain in Balance was to close exactly this class of window.

Either pattern is defensible per-site, but there's currently no principle documented for choosing between them. Your own follow-up idea — a useChain() hook that returns both selected and active (and matching useExplorerClusterName() variants) — feels like the right shape, since the two are semantically different values. Even without that hook landing in this PR, it might be worth a short comment at the top of ChainContext.tsx (or in ClientProvider.tsx) documenting the split so future contributors don't have to reverse-engineer which one to reach for.

Nice touches

  • The comments in Balance.tsx and routes/root.tsx explaining why the chain is now read from the client (rather than ChainContext) are exactly the kind of thing that saves future readers a lot of head-scratching. Please keep this quality of comment on the sibling components when they migrate.
  • The new refetches against the new client when the network (client) switches test is a great regression guard for the SWR-key-derived-from-client-chain invariant, and the comment inside it spells out precisely what the test is protecting against.
  • Extending the client with chain via extendClient (rather than adding a whole plugin just for that) is the right weight of solution for the example app.

Notes for subsequent reviewers

  • The as unknown as AppClient casts in the two test wrappers are minimal stubs — ClientProvider from @solana/react accepts Client<object>, and useClient<AppClient>() narrows on the consumer side, so a plain object with the fields the component reads is sufficient. Worth being aware of if these tests later cover code paths that touch more of the real client surface (plugin capabilities, dispose, etc.) — the stubs would silently satisfy TypeScript while the runtime shape diverges.
  • No changeset needed — examples-only changes.
  • The comment in SlotIndicator-test.browser.tsx correctly keeps ChainContext.Provider because SlotIndicator still reads solanaExplorerClusterName from it. Consistent with the file it's testing.
  • The docblock on ClientProvider.tsx still {@link}s WalletReadyGate, which isn't exported from this file. Pre-existing, not introduced here.

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

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

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

In my mind a chain change would trigger a new client creation and hydrate the components accordingly but you've got a lot more context than me on that one so I trust your judgement. We can also chat more about it next week IRL. ☺️

@mcintyre94
mcintyre94 force-pushed the react-app-rpc-plugin branch from 4b243e5 to 83f587d Compare July 27, 2026 14:00
Copilot AI review requested due to automatic review settings July 27, 2026 14:00

Copilot AI 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.

Pull request overview

This PR updates the examples/react-app to install and use the @solana/kit-plugin-rpc plugin via the app’s Kit client, consolidating RPC + wallet concerns into a generalized ClientProvider and removing the bespoke RPC context/provider wiring.

Changes:

  • Add @solana/kit-plugin-rpc to the example app and lockfile.
  • Generalize WalletClientProvider to ClientProvider and install the RPC plugin during client construction.
  • Update components/tests to source rpc/rpcSubscriptions from useClient<AppClient>(), and remove the old RpcContext / RpcContextProvider.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks @solana/kit-plugin-rpc (and its transitive deps) for the workspace.
examples/react-app/package.json Adds @solana/kit-plugin-rpc dependency to the example app.
examples/react-app/src/main.tsx Replaces nested RPC/wallet providers with the new consolidated ClientProvider.
examples/react-app/src/routes/root.tsx Switches active-chain reads to useClient<AppClient>() for stable remount/reset keys.
examples/react-app/src/context/ClientProvider.tsx Builds the Kit client with wallet + RPC plugins and publishes it via @solana/react’s ClientProvider.
examples/react-app/src/context/RpcContextProvider.tsx Removed (RPC state now comes from the client).
examples/react-app/src/context/RpcContext.tsx Removed (RPC state now comes from the client).
examples/react-app/src/components/SolanaSignTransactionFeaturePanel.tsx Reads RPC from the client instead of the removed RpcContext.
examples/react-app/src/components/SolanaSignAndSendTransactionFeaturePanel.tsx Reads RPC from the client instead of the removed RpcContext.
examples/react-app/src/components/SolanaPartialSignTransactionFeaturePanel.tsx Reads RPC/RPC subscriptions from the client instead of the removed RpcContext.
examples/react-app/src/components/SlotIndicator.tsx Reads RPC subscriptions from the client instead of the removed RpcContext.
examples/react-app/src/components/Balance.tsx Reads chain/RPC/RPC subscriptions from the client to keep SWR keys aligned with the active client.
examples/react-app/src/components/AirdropButton.tsx Reads RPC/RPC subscriptions from the client instead of the removed RpcContext.
examples/react-app/src/components/tests/SlotIndicator-test.browser.tsx Updates test wrapper to provide ClientProvider instead of RpcContext.
examples/react-app/src/components/tests/Balance-test.browser.tsx Updates test wrapper to provide ClientProvider and adds a test for refetching on client/network switch.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 10 to 14
export function AirdropButton({ address }: { address: Address }) {
const { chain, solanaExplorerClusterName } = useContext(ChainContext);
const { rpc, rpcSubscriptions } = useContext(RpcContext);
const { rpc, rpcSubscriptions } = useClient<AppClient>();

const isMainnet = chain === 'solana:mainnet';
Comment on lines 12 to 15
export function SlotIndicator() {
const { rpcSubscriptions } = useContext(RpcContext);
const { rpcSubscriptions } = useClient<AppClient>();
const { solanaExplorerClusterName } = useContext(ChainContext);
const source = useMemo(() => rpcSubscriptions.slotNotifications(), [rpcSubscriptions]);
Comment thread examples/react-app/src/components/SolanaSignAndSendTransactionFeaturePanel.tsx Outdated
Comment thread examples/react-app/src/components/SolanaSignTransactionFeaturePanel.tsx Outdated
Comment thread examples/react-app/src/components/SolanaPartialSignTransactionFeaturePanel.tsx Outdated

mcintyre94 commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

Merge activity

@mcintyre94
mcintyre94 force-pushed the react-app-rpc-plugin branch from 2c6bef9 to 76edaf0 Compare August 4, 2026 16:21
@socket-security

socket-security Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​solana/​kit-plugin-rpc@​0.13.07810010093100

View full report

Base automatically changed from react-app-wallet-plugin to main August 4, 2026 17:12
@mcintyre94
mcintyre94 force-pushed the react-app-rpc-plugin branch from 76edaf0 to 1ba30bd Compare August 4, 2026 17:12
@mcintyre94
mcintyre94 merged commit 234a72a into main Aug 5, 2026
12 checks passed
@mcintyre94
mcintyre94 deleted the react-app-rpc-plugin branch August 5, 2026 07:48
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🔎💬 Inkeep AI search and chat service is syncing content for source 'Solana Kit Docs'

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.

4 participants