@solana/react publishes a Kit client to your component tree through one
provider, then hooks read RPC data, subscriptions, and wallet state from it. The
browser wallet hooks live in @solana/kit-plugin-wallet/react and read the same
provider — there is no separate wallet provider.
Install
$npm install @solana/kit @solana/kit-plugin-rpc @solana/kit-plugin-wallet @solana/react
Build the client
Build the client once, at module scope, outside the React tree. In the browser,
walletSigner makes the connected wallet both the fee payer and the identity.
import { createClient } from "@solana/kit";import { solanaRpc } from "@solana/kit-plugin-rpc";import { walletSigner } from "@solana/kit-plugin-wallet";export const client = createClient().use(walletSigner({ chain: "solana:devnet" })).use(solanaRpc({ rpcUrl: "https://api.devnet.solana.com" }));export type AppClient = Awaited<typeof client>;
walletSigner adds client.wallet (Wallet Standard discovery and connection),
and solanaRpc adds client.rpc plus the client.sendTransaction helper.
Export the client type wrapped in Awaited<> so it stays correct if you later
add a plugin that resolves asynchronously, and pass that AppClient to every
useClient call.
Wrap your tree once
ClientProvider publishes the client. The data hooks (useRequest,
useSubscription, useTrackedData) read it via useClient. The wallet hooks
below need the same client passed in explicitly as their first argument.
"use client";import { ClientProvider } from "@solana/react";import { client } from "./client";export function Providers({ children }: { children: React.ReactNode }) {return <ClientProvider client={client}>{children}</ClientProvider>;}
Wallet hooks
The wallet hooks need a client with a wallet plugin installed (e.g.
walletSigner). Get it from useClient<AppClient>() and pass it as each hook's
first argument — this keeps the app fully typed end-to-end instead of relying on
context lookups.
useWallets(client)— Wallet Standard wallets discovered for the client's chain.useWalletStatus(client)—"pending" | "disconnected" | "connecting" | "connected" | "disconnecting" | "reconnecting".useConnectedWallet(client)—{ account, signer, wallet }ornull.signerisnullfor read-only wallets.useConnect(client)/useDisconnect(client)— return anActionResultwhosedispatch(anddispatchAsync) runs the action and whoseisRunning,status,data,error, andresettrack its result.
"use client";import {useConnect,useConnectedWallet,useDisconnect,useWallets,useWalletStatus} from "@solana/kit-plugin-wallet/react";import { useClient } from "@solana/react";import type { AppClient } from "./client";function WalletPanel() {const client = useClient<AppClient>();const status = useWalletStatus(client);const wallets = useWallets(client);const connected = useConnectedWallet(client);const connect = useConnect(client);const disconnect = useDisconnect(client);if (status === "pending") return null; // wait out auto-reconnectif (connected) {return (<div><p>{connected.account.address}</p><button onClick={() => disconnect.dispatch()}>Disconnect</button></div>);}return (<div>{wallets.map((wallet) => (<buttonkey={wallet.name}disabled={connect.isRunning}onClick={() => connect.dispatch(wallet)}>Connect {wallet.name}</button>))}</div>);}
Send a transaction
useClient returns the same client you published. Build instructions with a
generated program client and send them through client.sendTransaction.
"use client";import { address, lamports } from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";import { useClient } from "@solana/react";import { useConnectedWallet } from "@solana/kit-plugin-wallet/react";import type { AppClient } from "./client";function SendSol({ destination }: { destination: string }) {const client = useClient<AppClient>();const connected = useConnectedWallet(client);async function handleSend() {if (!connected?.signer) return;const transfer = getTransferSolInstruction({source: connected.signer,destination: address(destination),amount: lamports(10_000_000n) // 0.01 SOL});const result = await client.sendTransaction([transfer]);console.log("sent", result.context.signature);}return (<button disabled={!connected?.signer} onClick={handleSend}>Send 0.01 SOL</button>);}
Passing the exported AppClient type to useClient gives the hook the full
plugin-extended client so client.rpc and client.sendTransaction are typed.
Read data
@solana/react ships hooks that read through the client:
useRequest(source)— one-shot RPC read; returns{ data, error, status, refresh }.useSubscription(source)— a live subscription; returns{ data, error, reconnect, status }.useTrackedData(spec)— an RPC-seeded value kept fresh by a subscription.
"use client";import { address } from "@solana/kit";import { useClient, useRequest } from "@solana/react";import { useMemo } from "react";import type { AppClient } from "./client";function Balance({ owner }: { owner: string }) {const client = useClient<AppClient>();const source = useMemo(() => client.rpc.getBalance(address(owner)),[client, owner]);const { data, status, refresh } = useRequest(source);if (status === "fetching") return <p>Loading…</p>;if (status === "error") return <p role="alert">RPC error</p>;return (<div><p>Lamports: {data?.value.toString()}</p><button onClick={() => refresh()}>Refresh</button></div>);}
Memoize the source (with useMemo, or useCallback for a function) so the hook
refetches only when its inputs change rather than on every render.
For caching and revalidation, the @solana/react/swr and @solana/react/query
subpaths wrap the same hooks for SWR and
TanStack Query.
Common patterns for Solana devs
- One provider:
ClientProvideris the only provider you need; get the client withuseClient<AppClient>()and pass it into the wallet hooks. - Server components aware: Only mark leaf components that call hooks with
"use client"; server reads can use a plain Kit RPC client without a wallet. - Testing: Publish a mocked client through
ClientProviderto simulate wallets and RPC responses.
Two hook families, same names
useSignIn and useSignMessage exist in both @solana/react (they take a
UiWalletAccount argument) and @solana/kit-plugin-wallet/react (take the
client instead, return an ActionResult). Use the
@solana/kit-plugin-wallet/react versions with the client pattern above; the
older @solana/react wallet hooks are being superseded.
Pair this guide with the Kit client overview to understand the client each hook reads from.
Is this page helpful?