---
title: React
description:
  Share a Kit client across your React app with @solana/react and the wallet
  plugin hooks.
---

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

```tsx title="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.

```tsx title="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.

```tsx
"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`.

```tsx
"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.

```tsx
"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](https://swr.vercel.app) and
[TanStack Query](https://tanstack.com/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.

<Callout type="warn" title="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.
</Callout>

Pair this guide with the [Kit client overview](/docs/frontend/client) to
understand the client each hook reads from.
