Skip to content

Rework the react docs to prioritise useClient<AppClient> - #1840

Open
mcintyre94 wants to merge 1 commit into
mainfrom
docs-client-type
Open

Rework the react docs to prioritise useClient<AppClient>#1840
mcintyre94 wants to merge 1 commit into
mainfrom
docs-client-type

Conversation

@mcintyre94

@mcintyre94 mcintyre94 commented Jul 14, 2026

Copy link
Copy Markdown
Member

Problem

Initially I planned to write the react hooks around useClientCapability, which does a runtime check for the capability on the client under ClientProvider.

As discussed here though, this reduces type safety which plugins otherwise maintain all the way through the stack.

We therefore plan to focus on a pattern where type safety is maintained for react apps using a typed client

We also plan to have hooks that do depend on a plugin, eg the wallet hooks, take the client with that plugin as a param: anza-xyz/kit-plugins#326. This enables type safety across the whole app.

Summary of Changes

  • We prioritise a pattern where apps export AppClient, the type of their built client. Each component uses this in a useClient<AppClient>()
  • useClientCapability is de-emphasised as a runtime escape hook, not the intended way to build client-dependent plugins
  • All docs are updated to use this <AppClient> type, instead of manually typing a useClient call.

This all fits the existing API, it's just a shift in documentation and in how we intend to build the hooks that depend on the client.

Note that I decided not to add a createClientContext that creates a ClientProvider and a typed useClient, I don't think that additional API surface is warranted and I think it would be more confusing.

Also note that we plan to make the useClient type non-optional, this will be a follow up.

@changeset-bot

changeset-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: cd075bc

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 commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@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.26KB -
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 -
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 -
plugin-core/dist/index.browser.mjs
799B -
plugin-core/dist/index.native.mjs
798B -
plugin-core/dist/index.node.mjs
796B -
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

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.

Docs-only rework that reorients the React guide + README around useClient<AppClient>() as the primary pattern, with useClientCapability reframed as a runtime escape hatch. The AppClient = Awaited<typeof client> idiom is a nice ergonomic default and it drops a lot of the noisy per-call ClientWithRpc<...> & ClientWithRpcSubscriptions<...> narrowing from the examples. No changeset needed — docs-only.

A few things worth a look before merging:

1. The useClientCapability example undercuts the new narrative. Both files now open the section with "you rarely need this — when you type your client with useClient<AppClient>() the compiler already guarantees the capability is present," and then immediately show a hand-rolled useCheckedRpc wrapper around useClientCapability<ClientWithRpc<GetEpochInfoApi>>. That's exactly the shape of code the prose just said you shouldn't be writing, and the example doesn't illustrate the motivating scenario the prose describes ("a loosely-typed Client read from context, say"). Consider replacing it with an example that actually shows the escape-hatch case — e.g. a helper that takes a bare Client from somewhere and asserts a capability — or at minimum a comment on the example framing it as "if you find yourself with an untyped client, here's how you'd guard". Right now the example reads as "here's the pattern" while the prose says "don't do this."

2. Please verify the generatedSigner() async claim in packages/react/README.md. The new client.ts snippet has the comment `Awaited<…>` resolves the promise when any plugin is async (e.g. `generatedSigner()`); for a fully-synchronous client it is simply `typeof client`. That parenthetical asserts generatedSigner() is async, but the same composition is used elsewhere in docs/content/docs/guides/react/index.mdx at module scope as a synchronous const client = createClient()... and the async example there wraps it in a separate createClientWithAsyncPlugins() helper. If generatedSigner() returns synchronously, the parenthetical is misleading — either drop the example or pick a plugin that really is async.

3. Two comma splices worth cleaning up:

  • docs/content/docs/guides/react/core-hooks.mdx: "AppClient uses Awaited<typeof client> so it resolves the promise when a plugin is async, for a fully-synchronous client Awaited<…> is a no-op and typeof client alone would do." — the comma before "for a fully-synchronous client" should be a period or semicolon.
  • packages/react/README.md useClient intro: "It defaults to the base Client shape, pass your client's type through the generic to get the capabilities you installed typed at the call site." — same pattern, wants a period or semicolon before "pass".

4. Code-block structure in core-hooks.mdx. The useClient example is a single twoslash block that starts as client.ts (defines and exports client + AppClient), then has an import { useClient } from '@solana/react' in the middle, then "any component" with a FetchEpochButton. Reads a bit oddly as one file. The README handles the same content with two separate code blocks (one for client.ts, one for the component importing AppClient from ./client), which is much clearer. Would be nice to align — either two twoslash blocks with a shared prelude, or a stronger visual break inside the single block. Not blocking, but the README version is easier to follow.

Notes for subsequent reviewers:

  • Worth checking whether the reframe is consistent with any other places that reference useClientCapability in docs (e.g. the ClientProvider intro in the README still lists useClientCapability in the same breath as useClient as "Required for…"; that's fine, but if the intent is really to demote it, it may deserve a lighter mention).
  • The Choosing a hook table in index.mdx now describes useClientCapability as "Runtime-guard a capability on an untyped client" — nice framing that matches the new narrative.
  • Sanity-check the twoslash snippets actually compile with the new imports (@solana/kit-plugin-rpc, @solana/kit-plugin-signer) — these look right based on the existing index.mdx, but a local pnpm docs:build (or however docs are built) would confirm.

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

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

@mcintyre94

Copy link
Copy Markdown
Member Author

The useClientCapability example undercuts the new narrative.

Updated this, now just specifies that the Client is untyped.

Please verify the generatedSigner() async claim

It's async

Two comma splices worth cleaning up

I think these read fine

Code-block structure in core-hooks.mdx

This is intentional, to show AppClient before the first time we use it on this page.

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 @solana/react documentation to prioritize a typed-client pattern (AppClient + useClient<AppClient>()) so React apps retain end-to-end type safety from their client’s actual plugin composition, while repositioning useClientCapability as an opt-in runtime assertion for loosely typed clients.

Changes:

  • Updates @solana/react README examples to encourage exporting AppClient = Awaited<typeof client> and using useClient<AppClient>().
  • Revises the React guide docs to promote typed-client usage and to de-emphasize useClientCapability as the primary way to access capabilities.
  • Updates hook-selection guidance and examples across the React docs to reflect the typed-client approach.

Reviewed changes

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

File Description
packages/react/README.md Updates README narrative + examples to use AppClient and typed useClient<AppClient>(); reframes useClientCapability.
docs/content/docs/guides/react/index.mdx Exports client and AppClient in the setup snippet and updates the “Choosing a hook” table to emphasize typed clients.
docs/content/docs/guides/react/core-hooks.mdx Updates core hook docs and examples to use the AppClient pattern and repositions useClientCapability as a runtime escape hatch.

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

Comment thread packages/react/README.md
### `ClientProvider`

Publishes a caller-owned Kit client to its subtree. Required for `useClient`, `useClientCapability`, and any plugin-specific hook that depends on a client capability. Generic primitives like `useAction` work against arbitrary async functions and don't need a provider.
Publishes a caller-owned Kit client to its subtree. Required for `useClient` and any plugin-specific hook that reads a client capability. Generic primitives like `useAction` work against arbitrary async functions and don't need a provider.
Comment thread packages/react/README.md
capability: 'rpc',
hookName: 'useRpc',
hookName: 'EpochBadge',
providerHint: 'Install `solanaRpc()` on the client.',
capability: 'rpc',
hookName: 'useRpc',
hookName: 'EpochBadge',
providerHint: 'Install a `solanaRpc()` plugin on the client.',
Comment thread packages/react/README.md
}
```

`useClient` is a pure type assertion with no runtime check.

`AppClient` uses `Awaited<typeof client>` so it resolves the promise when a plugin is async, for a fully-synchronous client `Awaited<…>` is a no-op and `typeof client` alone would do.

`useClient` is a pure type-cast with no runtime check.
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