React

@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

Terminal
$
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.

client.ts
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.

providers.tsx
"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 } or null. signer is null for read-only wallets.
  • useConnect(client) / useDisconnect(client) — return an ActionResult whose dispatch (and dispatchAsync) runs the action and whose isRunning, status, data, error, and reset track 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-reconnect
if (connected) {
return (
<div>
<p>{connected.account.address}</p>
<button onClick={() => disconnect.dispatch()}>Disconnect</button>
</div>
);
}
return (
<div>
{wallets.map((wallet) => (
<button
key={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: ClientProvider is the only provider you need; get the client with useClient<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 ClientProvider to 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?

Table of Contents

Edit Page
© 2026 Solana Foundation. All rights reserved.