[SYS]KK.PORTFOLIO / V1.0
Back to Blog

DApp Development

Smart Contract Integration with React

Connect a React frontend to a deployed contract with verified ABIs and addresses, reliable reads, transaction states, chain switching, and testable adapters.

Ketan Kachariya 4 min read

React frontend components connected to a smart contract through an ABI
On This Page

A React integration should translate contract behavior into a predictable product workflow. That requires more than importing an ABI and calling a method. The frontend must select the correct address for the active chain, distinguish public reads from signed writes, keep asynchronous state current, and explain wallet or contract failures. A thin integration layer makes those responsibilities explicit and keeps blockchain details from leaking through every component.

Define the frontend-to-contract architecture

Use four layers: typed deployment configuration, a provider and wallet adapter, contract-specific read/write functions, and React hooks or components. Deployment configuration maps chain IDs to verified addresses and explorer URLs. The wallet adapter exposes normalized account and network state. Contract functions encode the ABI-facing operations. React owns presentation, input state, and lifecycle. This design makes it possible to test a component with mocked contract operations and to test the adapter independently against a local chain.

Treat ABIs and addresses as versioned configuration

The ABI is the client contract. Generate it from the same reviewed build that produced the deployment, then expose only required fragments when that improves clarity. Store addresses by environment and chain ID, not in scattered components. Before a write, check the selected network and optionally verify that bytecode exists at the address. For proxy-based deployments, record the proxy address and expected interface version. A frontend release should be traceable to the contract deployments it supports.

Separate read and write operations

Reads can use a public provider without connecting a wallet. This lets visitors inspect state before granting account access and reduces unnecessary prompts. Writes require a signer and should begin only after input validation, account detection, and a chain check. Expose read functions that return application-friendly values instead of raw Result objects. Expose write functions that return a transaction response so the UI can show the hash and manage confirmation explicitly.

typescript
import { Contract, JsonRpcProvider, BrowserProvider } from "ethers";

const abi = [
  "function message() view returns (string)",
  "function setMessage(string nextMessage)",
];

export async function getMessage(rpcUrl: string, address: string) {
  const contract = new Contract(address, abi, new JsonRpcProvider(rpcUrl));
  return (await contract.message()) as string;
}

export async function setMessage(address: string, nextMessage: string) {
  if (!window.ethereum) throw new Error("Wallet unavailable");
  const provider = new BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();
  const contract = new Contract(address, abi, signer);
  return await contract.setMessage(nextMessage);
}

Model pending transactions as a state machine

Use distinct states for awaiting wallet approval, submitted, confirming, confirmed, and failed. The user may reject before submission, or the transaction may revert later. Preserve the transaction hash and surface an explorer link built from trusted chain configuration. Prevent duplicate submits while a request is unresolved, but let the visitor navigate elsewhere. After confirmation, invalidate the relevant read query or reconcile the event rather than mutating the screen optimistically without proof.

Handle errors and chain switching deliberately

Validate input before a wallet prompt. Normalize common wallet rejection, unsupported-chain, RPC, and contract-revert cases into actionable UI messages while retaining technical detail in logs. If the user selects Switch Network, request only a known chain and handle the wallet's unsupported-chain response before offering to add configuration. A chain change should recreate contracts and cancel stale requests. Never continue a write using an address selected for the previous chain.

Test the integration from both sides

Unit-test formatting, input rules, and state transitions without a chain. Test contract adapters against a deterministic local network for successful reads, expected reverts, events, and account permissions. Component tests can inject a fake adapter to simulate rejected signatures, pending receipts, and network changes. Add at least one browser-level flow with a controlled test wallet or provider harness. Mainnet forking may help with real interfaces, but pinned blocks and explicit assumptions are essential for repeatability.

  • Wrong chain with a valid-looking contract address
  • Wallet connected, then account removed or changed
  • User rejects a signature or transaction
  • RPC read times out while a write is still pending
  • Transaction is mined with a failed receipt
  • Frontend ABI does not match the deployed interface

Keep server, wallet, and transaction state distinct

Contract reads resemble server state because they are asynchronous snapshots owned by an external system. Cache them with keys that include chain ID, address, function, arguments, and a freshness policy. Wallet connection is session state controlled by an injected provider, while a pending form is local UI state. Combining all three in one global object creates stale signer references and broad rerenders. Invalidate only the queries affected by a confirmed transaction, and use emitted events as hints to refetch rather than assuming every event payload is the complete current state.

A write hook should expose stages that components can render without interpreting provider internals: validating, requesting signature, submitted with a hash, confirming, succeeded, or failed. Preserve the request context until the receipt is known, and guard against a response from an old account or chain overwriting the current screen. On unmount, stop updating component state, but do not pretend the transaction stopped. A later screen or restored session should be able to recover its status from the hash.

Keep the contract boundary narrow

A component should ask for a domain action such as loadProfile or submitVote, not assemble providers and ABIs inline. Narrow adapters turn on-chain mechanics into testable application operations. They also make wallet libraries, RPC providers, and contract versions easier to change. The result is a React codebase that acknowledges blockchain uncertainty without making every screen responsible for it.

ReactSmart ContractsABIEthers.jsFrontend Testing

Building a Web3 Product?

Need help with a DApp, smart contract integration, wallet connection, or production Web3 architecture? Let's discuss the engineering behind your product.

Discuss Your Web3 Project

Keep Reading

React interface connected through a wallet to an Ethereum smart contract

DApp Development

Featured

Build a Web3 DApp with React and Ethereum

A practical architecture guide for connecting a React interface to Ethereum, reading contract state, sending transactions, and designing dependable wallet-aware UI.

Oct 19, 2026 5 min read
Read Article