Add the RPC plugin to the react-app example - #1844
Conversation
BundleMonUnchanged files (150)
No change in files bundle size Final result: ✅ View report in BundleMon website ➡️ |
260bb1e to
4b243e5
Compare
|
trevor-cortex
left a comment
There was a problem hiding this comment.
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(fromChainContext) disables the button based on the selected chain, but the airdrop actually runs againstclient.rpc. In practice this fails safe (the mainnet rpc has norequestAirdrop, and the mainnet→devnet direction just hits a devnet rpc that supports it), but the eager/lagging split is worth being deliberate about.solanaExplorerClusterNameinSlotIndicator,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 inBalancewas 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.tsxandroutes/root.tsxexplaining why the chain is now read from the client (rather thanChainContext) 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) switchestest 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
chainviaextendClient(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 AppClientcasts in the two test wrappers are minimal stubs —ClientProviderfrom@solana/reactacceptsClient<object>, anduseClient<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.tsxcorrectly keepsChainContext.ProviderbecauseSlotIndicatorstill readssolanaExplorerClusterNamefrom it. Consistent with the file it's testing. - The docblock on
ClientProvider.tsxstill{@link}sWalletReadyGate, which isn't exported from this file. Pre-existing, not introduced here.
|
Documentation Preview: https://kit-docs-l4pj3yd5s-anza-tech.vercel.app |
lorisleiva
left a comment
There was a problem hiding this comment.
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.
4b243e5 to
83f587d
Compare
There was a problem hiding this comment.
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-rpcto the example app and lockfile. - Generalize
WalletClientProvidertoClientProviderand install the RPC plugin during client construction. - Update components/tests to source
rpc/rpcSubscriptionsfromuseClient<AppClient>(), and remove the oldRpcContext/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.
| 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'; |
| export function SlotIndicator() { | ||
| const { rpcSubscriptions } = useContext(RpcContext); | ||
| const { rpcSubscriptions } = useClient<AppClient>(); | ||
| const { solanaExplorerClusterName } = useContext(ChainContext); | ||
| const source = useMemo(() => rpcSubscriptions.slotNotifications(), [rpcSubscriptions]); |
5288c5b to
92d8a66
Compare
92d8a66 to
64bccf9
Compare
64bccf9 to
2c6bef9
Compare
Merge activity
|
2c6bef9 to
76edaf0
Compare
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
76edaf0 to
1ba30bd
Compare
|
🔎💬 Inkeep AI search and chat service is syncing content for source 'Solana Kit Docs' |

This PR adds the
solanaRpcplugin to the example app, and generalises itsWalletClientProviderto justClientProvider. It replaces use ofrpcandrpcSubscriptionsto come from the client, and removes the oldRpcContextProviderthat 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 installssolanaRpcso that future PRs can.An issue this caused is that the
rpcnow 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 fromChainContextcan be out of sync with therpc. We useextendClientto 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 theChainContextso that it updates immediately.This leads to 2 points:
chainandsetChain, and we could add auseChainreact hookManagedClientProvider(AddManagedClientProviderto@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.