CYBERVISION
    [ CONSULTING ]
    AboutServicesProjectsBlogContact
    Book a Call
    CYBERVISION
    AboutServicesProjectsBlogContact

    © 2026 Cyber Vision Consulting

    Back to Insights
    SolanaReactWeb3TypeScript

    Solana React Basics: Building Your First dApp

    A hands-on guide to connecting a React frontend to the Solana blockchain — wallet adapters, reading on-chain data, sending transactions, and integrating Anchor programs.

    C

    Cyber Vision

    February 20, 2026

    Why Solana + React?

    Solana processes thousands of transactions per second with sub-cent fees. React is the dominant frontend framework. Together they form the most practical stack for building decentralized applications that actually feel like modern web apps.

    This guide walks you through the essential building blocks: connecting wallets, reading accounts, sending transactions, and calling Anchor programs — all from a React frontend.

    Prerequisites

    • React 18+ project (Next.js, Vite, or CRA)
    • Basic understanding of Solana (accounts, transactions, programs)
    • A browser wallet like Phantom or Solflare

    1. Install the Wallet Adapter

    The @solana/wallet-adapter packages provide a unified interface for every Solana wallet.

    npm install @solana/wallet-adapter-base \
      @solana/wallet-adapter-react \
      @solana/wallet-adapter-react-ui \
      @solana/wallet-adapter-wallets \
      @solana/web3.js

    2. Set Up the Provider

    Wrap your app with the wallet and connection providers. This gives every child component access to the connected wallet and the RPC connection.

    // src/providers/WalletProvider.tsx
    import { useMemo } from "react";
    import {
      ConnectionProvider,
      WalletProvider,
    } from "@solana/wallet-adapter-react";
    import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
    import { PhantomWalletAdapter } from "@solana/wallet-adapter-wallets";
    import { clusterApiUrl } from "@solana/web3.js";
     
    import "@solana/wallet-adapter-react-ui/styles.css";
     
    export function SolanaProviders({
      children,
    }: {
      children: React.ReactNode;
    }) {
      const endpoint = useMemo(
        () => clusterApiUrl("devnet"),
        []
      );
     
      const wallets = useMemo(
        () => [new PhantomWalletAdapter()],
        []
      );
     
      return (
        <ConnectionProvider endpoint={endpoint}>
          <WalletProvider wallets={wallets} autoConnect>
            <WalletModalProvider>
              {children}
            </WalletModalProvider>
          </WalletProvider>
        </ConnectionProvider>
      );
    }

    Then wrap your app root:

    // src/App.tsx or layout.tsx
    import { SolanaProviders } from "./providers/WalletProvider";
     
    export default function App({ children }) {
      return (
        <SolanaProviders>
          {children}
        </SolanaProviders>
      );
    }

    3. The Connect Wallet Button

    The wallet adapter ships a ready-made button with a modal that lists all detected wallets.

    import {
      WalletMultiButton,
    } from "@solana/wallet-adapter-react-ui";
     
    export function Navbar() {
      return (
        <nav>
          <h1>My Solana dApp</h1>
          <WalletMultiButton />
        </nav>
      );
    }

    That single component handles connect, disconnect, wallet selection, and displaying the truncated public key — all out of the box.

    ✅

    You can customize the button styling with the className prop or wrap useWallet() to build a fully custom connect experience.

    4. Reading On-Chain Data

    Use the useConnection hook to access the RPC connection and fetch account data.

    Fetch SOL Balance

    import { useConnection, useWallet } from "@solana/wallet-adapter-react";
    import { LAMPORTS_PER_SOL } from "@solana/web3.js";
    import { useEffect, useState } from "react";
     
    export function Balance() {
      const { connection } = useConnection();
      const { publicKey } = useWallet();
      const [balance, setBalance] = useState<number | null>(null);
     
      useEffect(() => {
        if (!publicKey) return;
     
        const fetchBalance = async () => {
          const lamports = await connection.getBalance(publicKey);
          setBalance(lamports / LAMPORTS_PER_SOL);
        };
     
        fetchBalance();
     
        const id = connection.onAccountChange(publicKey, (info) => {
          setBalance(info.lamports / LAMPORTS_PER_SOL);
        });
     
        return () => {
          connection.removeAccountChangeListener(id);
        };
      }, [connection, publicKey]);
     
      if (!publicKey) return <p>Connect your wallet</p>;
     
      return (
        <div>
          <p>Balance: {balance?.toFixed(4)} SOL</p>
        </div>
      );
    }

    The onAccountChange subscription gives you real-time updates whenever the balance changes — no polling required.

    Fetch Token Accounts

    import { useConnection, useWallet } from "@solana/wallet-adapter-react";
    import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
     
    export function useTokenAccounts() {
      const { connection } = useConnection();
      const { publicKey } = useWallet();
     
      const fetchTokens = async () => {
        if (!publicKey) return [];
     
        const response = await connection.getParsedTokenAccountsByOwner(
          publicKey,
          { programId: TOKEN_PROGRAM_ID }
        );
     
        return response.value.map((account) => ({
          mint: account.account.data.parsed.info.mint,
          balance: account.account.data.parsed.info.tokenAmount.uiAmount,
          decimals: account.account.data.parsed.info.tokenAmount.decimals,
        }));
      };
     
      return { fetchTokens };
    }

    5. Sending Transactions

    Simple SOL Transfer

    import { useConnection, useWallet } from "@solana/wallet-adapter-react";
    import {
      PublicKey,
      SystemProgram,
      Transaction,
      LAMPORTS_PER_SOL,
    } from "@solana/web3.js";
     
    export function SendSol() {
      const { connection } = useConnection();
      const { publicKey, sendTransaction } = useWallet();
     
      const handleSend = async () => {
        if (!publicKey) return;
     
        const recipient = new PublicKey("TARGET_WALLET_ADDRESS");
     
        const transaction = new Transaction().add(
          SystemProgram.transfer({
            fromPubkey: publicKey,
            toPubkey: recipient,
            lamports: 0.01 * LAMPORTS_PER_SOL,
          })
        );
     
        const { blockhash } = await connection.getLatestBlockhash();
        transaction.recentBlockhash = blockhash;
        transaction.feePayer = publicKey;
     
        const signature = await sendTransaction(transaction, connection);
        console.log("Transaction sent:", signature);
     
        await connection.confirmTransaction(signature, "confirmed");
        console.log("Transaction confirmed!");
      };
     
      return (
        <button onClick={handleSend} disabled={!publicKey}>
          Send 0.01 SOL
        </button>
      );
    }

    The sendTransaction function from the wallet adapter handles wallet signing and submission in one call.

    6. Integrating an Anchor Program

    If your on-chain program is built with Anchor, the client integration is even smoother thanks to the generated IDL and type-safe methods.

    npm install @coral-xyz/anchor
    import { useConnection, useWallet } from "@solana/wallet-adapter-react";
    import { Program, AnchorProvider } from "@coral-xyz/anchor";
    import { PublicKey } from "@solana/web3.js";
    import idl from "./idl/counter.json";
     
    const PROGRAM_ID = new PublicKey("YOUR_PROGRAM_ID");
     
    export function useCounterProgram() {
      const { connection } = useConnection();
      const wallet = useWallet();
     
      const getProgram = () => {
        const provider = new AnchorProvider(
          connection,
          wallet as any,
          { commitment: "confirmed" }
        );
        return new Program(idl as any, PROGRAM_ID, provider);
      };
     
      const increment = async () => {
        const program = getProgram();
     
        const [counterPda] = PublicKey.findProgramAddressSync(
          [Buffer.from("counter")],
          program.programId
        );
     
        await program.methods
          .increment()
          .accounts({ counter: counterPda })
          .rpc();
      };
     
      const fetchCount = async () => {
        const program = getProgram();
     
        const [counterPda] = PublicKey.findProgramAddressSync(
          [Buffer.from("counter")],
          program.programId
        );
     
        const account = await program.account.counter.fetch(counterPda);
        return account.count.toNumber();
      };
     
      return { increment, fetchCount };
    }
    💡

    Copy the IDL from target/idl/your_program.json after running anchor build. The IDL gives the Anchor client full type information about your program's instructions and accounts.

    7. Error Handling Patterns

    Solana transactions can fail for many reasons. Here is a robust pattern:

    import { WalletNotConnectedError } from "@solana/wallet-adapter-base";
     
    async function safeSend(
      sendTransaction: Function,
      transaction: Transaction,
      connection: Connection,
      publicKey: PublicKey | null
    ) {
      if (!publicKey) throw new WalletNotConnectedError();
     
      try {
        const { blockhash, lastValidBlockHeight } =
          await connection.getLatestBlockhash();
     
        transaction.recentBlockhash = blockhash;
        transaction.feePayer = publicKey;
     
        const signature = await sendTransaction(transaction, connection);
     
        const confirmation = await connection.confirmTransaction(
          { signature, blockhash, lastValidBlockHeight },
          "confirmed"
        );
     
        if (confirmation.value.err) {
          throw new Error(
            `Transaction failed: ${JSON.stringify(confirmation.value.err)}`
          );
        }
     
        return signature;
      } catch (error: any) {
        if (error.message?.includes("User rejected")) {
          console.log("User cancelled the transaction");
          return null;
        }
        throw error;
      }
    }

    8. Useful Hooks Cheat Sheet

    HookPackagePurpose
    useWallet()@solana/wallet-adapter-reactAccess connected wallet, publicKey, signTransaction
    useConnection()@solana/wallet-adapter-reactAccess the Solana RPC connection
    useAnchorWallet()@solana/wallet-adapter-reactWallet object compatible with AnchorProvider
    useWalletModal()@solana/wallet-adapter-react-uiProgrammatically open/close wallet selection modal

    Project Structure Recommendation

    src/
    ├── providers/
    │   └── WalletProvider.tsx
    ├── hooks/
    │   ├── useBalance.ts
    │   ├── useTokenAccounts.ts
    │   └── useProgram.ts        # Anchor program hook
    ├── components/
    │   ├── Navbar.tsx            # WalletMultiButton
    │   ├── Balance.tsx
    │   ├── SendSol.tsx
    │   └── ProgramInteraction.tsx
    ├── idl/
    │   └── your_program.json    # Anchor IDL
    └── App.tsx
    

    Wrapping Up

    With the Solana wallet adapter and a few hundred lines of React, you can build fully functional decentralized applications. The key building blocks are:

    1. Providers — ConnectionProvider + WalletProvider wrap your app
    2. Wallet hooks — useWallet() and useConnection() give you everything
    3. Transactions — build with Transaction, sign with sendTransaction
    4. Anchor integration — Program class with IDL for type-safe program calls
    5. Subscriptions — onAccountChange for real-time UI updates

    From here, explore SPL tokens, Metaplex NFTs, or build a full DEX interface. The React patterns stay the same — only the program interactions change.

    Enjoyed this article? Explore more engineering insights.More articles

    Comments

    Leave a comment