BlogWeb3 Frontend Development: Connecting React to Smart Contracts
Technical9 min read

Web3 Frontend Development: Connecting React to Smart Contracts

NB

Nitish Beejawat

Founder, Tantrija Enterprises

Share

Contents

  1. 1The modern Web3 frontend stack
  2. 2Contract interaction patterns
  3. 3Handling the wallet UX properly
  4. 4Reading on-chain data efficiently
  5. 5The user authentication question

Web3 frontend development has a reputation for being unnecessarily complicated. With the right stack, it is not. Here is the practical guide to connecting a React application to smart contracts in 2024 — tools that are actually production-ready, patterns that work, and the UX pitfalls to avoid.

The modern Web3 frontend stack

The ecosystem has converged on a clean stack. Viem is the low-level Ethereum interface library — think of it as a type-safe replacement for ethers.js with better tree-shaking and TypeScript support. Wagmi builds on Viem with React hooks for wallet connection, contract reads, and transaction management. RainbowKit or ConnectKit handle the wallet connection UI.

In practice: viem handles the raw JSON-RPC calls and ABI encoding/decoding. Wagmi provides useAccount, useReadContract, useWriteContract, and useWaitForTransactionReceipt hooks that manage loading states, error states, and cache invalidation. RainbowKit provides the "Connect Wallet" button and modal with support for MetaMask, WalletConnect, Coinbase Wallet, and dozens more.

The older ethers.js + web3-react stack still works and has more existing examples, but wagmi/viem is cleaner, better TypeScript support, and where the ecosystem is moving. For new projects, start with wagmi.

Contract interaction patterns

Reading from a contract (a view or pure function) uses useReadContract. The hook returns data, isLoading, and error. The result is automatically cached and refetched when the block number changes.

Writing to a contract uses useWriteContract. This triggers a wallet signature request when called. The writeContract function returns a transaction hash once the user confirms. useWaitForTransactionReceipt waits for the transaction to be mined and returns the receipt.

The pattern looks like: - useWriteContract() gives you a write function - Call write() to prompt wallet signature - Monitor isLoading while the user approves - Store the returned hash - useWaitForTransactionReceipt(hash) monitors mining - Show success/error based on receipt status

The most common mistake is treating transaction submission as completion. Submitting a transaction returns a hash immediately. The transaction may take 15 seconds to 2 minutes to be mined. Users who see a success message after submission and then find the transaction failed have poor experiences. Always wait for the receipt.

Handling the wallet UX properly

Wallet UX is where most Web3 applications lose non-crypto-native users. A few patterns that matter:

Never assume the user has a wallet. If window.ethereum is undefined, show a "Get a Wallet" flow rather than a broken interface. RainbowKit handles this by showing wallet installation options in the connect modal.

Never assume the user is on the right network. If your contract is on Polygon but the user's wallet is on Ethereum mainnet, every contract interaction will fail. Detect the chain after connection and prompt the user to switch. useNetwork() from wagmi provides the current chain, and useSwitchChain() lets you prompt a network switch programmatically.

Transaction status communication is critical. The user needs to know: "please approve in your wallet" (waiting for signature), "transaction submitted" (in the mempool), "transaction confirmed" (mined), or "transaction failed." Each of these states requires different UI feedback. Toast notifications with the right state mapping prevent confusion.

Pending states for optimistic UI: DeFi applications often update the UI immediately after transaction submission (before confirmation) to reduce the perceived wait time. Show the expected post-transaction state with a loading indicator, then confirm or revert based on the receipt.

Reading on-chain data efficiently

Reading large amounts of on-chain data naively creates performance problems. Calling useReadContract 50 times in a list rendering will result in 50 separate JSON-RPC calls to your RPC provider, potentially hitting rate limits and creating slow initial load times.

Use multicall (built into wagmi's useReadContracts hook, note the plural) to batch multiple contract reads into a single RPC call. A list of token balances for 50 addresses becomes one RPC call instead of 50.

For data that changes infrequently, cache aggressively. wagmi's query options let you configure refetch intervals. Pool state that changes every block should refetch every block. User NFT ownership that changes rarely can refetch only on user action.

The Graph protocol provides a powerful alternative for complex queries. Instead of reading contract state directly, The Graph indexes events emitted by your contract into a GraphQL API. Complex queries ("all NFTs owned by this address with price greater than X") that would require many on-chain reads become single GraphQL queries. For DeFi applications with complex data requirements, The Graph is often the right architecture for the data layer.

The user authentication question

Web3 applications have a choice between wallet-based authentication (signing a message with the wallet proves identity without a password) and hybrid authentication (wallet signs in, backend issues a session JWT).

Sign-In With Ethereum (SIWE, EIP-4361) is the standard for wallet-based authentication. The user signs a structured message proving wallet ownership. Your backend verifies the signature and issues a session. This is the correct approach for applications where the wallet is the identity.

Hybrid auth with JWTs enables server-side features — per-user settings, rate limiting, off-chain data association — while maintaining the wallet as the identity proof. Most production Web3 applications use this pattern: wallet signs in, backend issues a JWT, JWT is used for API calls.

Full decentralization (no backend, no sessions, all state on-chain) is appropriate for pure DeFi applications where there is no server-side logic. For any application with user accounts, notifications, or off-chain features, a backend is necessary and hybrid auth is the right model.

NB

Nitish Beejawat

Founder, Tantrija Enterprises

Nitish Beejawat is the founder of Tantrija Enterprises and led core L1 protocol development on Layer One X — a custom Layer 1 blockchain built from scratch. He has 6+ years of production blockchain engineering experience across DeFi, enterprise blockchain, and custom chain development.

linkedin.com/in/nitish-beejawat
/ Get Started

Building a Web3 application or dApp?

Full-stack Web3 development — smart contracts, indexing, and frontend — is our standard capability.

No sales pitch. Just an honest technical conversation.