React & Blockchain Integration: A Developer Guide
Introduction
Integrating blockchain technology with React applications opens up exciting possibilities for decentralized applications, smart contracts, and Web3 functionality. This guide will walk you through the process step by step.
Prerequisites
1. Development Environment Setup
Required Tools
- Node.js (v16 or higher)
- MetaMask browser extension
- Hardhat or Truffle for smart contract development
- React (v18 or higher)
Project Setup
<h1 id="create new react app" class="heading-1">Create new React app</h1>
npx create-react-app blockchain-react-app
cd blockchain-react-app
<h1 id="install blockchain dependencies" class="heading-1">Install blockchain dependencies</h1>
npm install ethers hardhat @nomiclabs/hardhat-waffle ethereum-waffle
npm install --save-dev @nomiclabs/hardhat-ethers
Smart Contract Development
1. Writing Smart Contracts
Sample Smart Contract
// contracts/SimpleStorage.sol pragma solidity ^0.8.0; contract SimpleStorage { uint256 private storedData; event DataStored(uint256 indexed newValue, address indexed storedBy); function store(uint256 _data) public { storedData = _data; emit DataStored(_data, msg.sender); } function retrieve() public view returns (uint256) { return storedData; } function increment() public { storedData += 1; emit DataStored(storedData, msg.sender); } }
2. Hardhat Configuration
// hardhat.config.js
module.exports = {
solidity: {
version: "0.8.0",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
hardhat: {
chainId: 31337
},
localhost: {
url: "http://127.0.0.1:8545"
}
}
};
React Integration
1. Web3 Provider Setup
Ethers.js Integration
// src/utils/web3.ts
import { ethers } from 'ethers';
declare global {
interface Window {
ethereum?: any;
}
}
export const connectWallet = async () => {
if (typeof window.ethereum !== 'undefined') {
try {
await window.ethereum.request({ method: 'eth_requestAccounts' });
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
return { provider, signer };
} catch (error) {
console.error('Error connecting wallet:', error);
throw error;
}
} else {
throw new Error('MetaMask is not installed');
}
};
export const getContract = async (contractAddress: string, contractABI: any) => {
const { signer } = await connectWallet();
return new ethers.Contract(contractAddress, contractABI, signer);
};
2. React Component for Blockchain Interaction
// src/components/BlockchainInteraction.tsx
import React, { useState, useEffect } from 'react';
import { connectWallet, getContract } from '../utils/web3';
interface ContractData {
storedValue: string;
isLoading: boolean;
error: string | null;
}
const CONTRACT_ADDRESS = '0x1234567890123456789012345678901234567890';
const CONTRACT_ABI = [
{
"inputs": [],
"name": "retrieve",
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{"internalType": "uint256", "name": "_data", "type": "uint256"}],
"name": "store",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];
export const BlockchainInteraction: React.FC = () => {
const [contractData, setContractData] = useState<ContractData>({
storedValue: '0',
isLoading: false,
error: null
});
const [inputValue, setInputValue] = useState('');
const [account, setAccount] = useState<string>('');
useEffect(() => {
const initContract = async () => {
try {
const contract = await getContract(CONTRACT_ADDRESS, CONTRACT_ABI);
const value = await contract.retrieve();
setContractData(prev => ({ ...prev, storedValue: value.toString() }));
// Get connected account
const { provider } = await connectWallet();
const accounts = await provider.listAccounts();
setAccount(accounts[0]);
} catch (error) {
setContractData(prev => ({ ...prev, error: error.message }));
}
};
initContract();
}, []);
const handleStore = async () => {
if (!inputValue) return;
setContractData(prev => ({ ...prev, isLoading: true, error: null }));
try {
const contract = await getContract(CONTRACT_ADDRESS, CONTRACT_ABI);
const tx = await contract.store(inputValue);
await tx.wait(); // Wait for transaction confirmation
// Refresh stored value
const newValue = await contract.retrieve();
setContractData(prev => ({
...prev,
storedValue: newValue.toString(),
isLoading: false
}));
setInputValue('');
} catch (error) {
setContractData(prev => ({
...prev,
error: error.message,
isLoading: false
}));
}
};
return (
<div className="blockchain-component">
<h2>Blockchain Storage Contract</h2>
{account && (
<p>Connected Account: {account.substring(0, 6)}...{account.substring(38)}</p>
)}
<div>
<h3>Current Stored Value: {contractData.storedValue}</h3>
<div>
<input
type="number"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Enter value to store"
disabled={contractData.isLoading}
/>
<button
onClick={handleStore}
disabled={contractData.isLoading || !inputValue}
>
{contractData.isLoading ? 'Storing...' : 'Store Value'}
</button>
</div>
</div>
{contractData.error && (
<div className="error">
Error: {contractData.error}
</div>
)}
</div>
);
};
Advanced Features
1. Real-time Event Listening
// src/hooks/useContractEvents.ts
import { useEffect, useState } from 'react';
import { getContract } from '../utils/web3';
export const useContractEvents = (contractAddress: string, contractABI: any) => {
const [events, setEvents] = useState<any[]>([]);
useEffect(() => {
const setupEventListener = async () => {
try {
const contract = await getContract(contractAddress, contractABI);
contract.on('DataStored', (newValue, storedBy) => {
setEvents(prev => [...prev, {
type: 'DataStored',
newValue: newValue.toString(),
storedBy,
timestamp: new Date()
}]);
});
return () => {
contract.removeAllListeners('DataStored');
};
} catch (error) {
console.error('Error setting up event listener:', error);
}
};
const cleanup = setupEventListener();
return cleanup;
}, [contractAddress, contractABI]);
return events;
};
2. Transaction Status Management
// src/utils/transactions.ts
interface Transaction {
hash: string;
status: 'pending' | 'confirmed' | 'failed';
from: string;
to: string;
value?: string;
timestamp: Date;
}
class TransactionManager {
private transactions: Map<string, Transaction> = new Map();
async submitTransaction(transaction: any): Promise<string> {
const txHash = transaction.hash;
this.transactions.set(txHash, {
hash: txHash,
status: 'pending',
from: transaction.from,
to: transaction.to,
value: transaction.value?.toString(),
timestamp: new Date()
});
// Wait for confirmation
const receipt = await transaction.wait();
if (receipt.status === 1) {
this.transactions.set(txHash, {
...this.transactions.get(txHash)!,
status: 'confirmed'
});
} else {
this.transactions.set(txHash, {
...this.transactions.get(txHash)!,
status: 'failed'
});
}
return txHash;
}
getTransaction(hash: string): Transaction | undefined {
return this.transactions.get(hash);
}
getAllTransactions(): Transaction[] {
return Array.from(this.transactions.values());
}
}
Best Practices
1. Security Considerations
- Always validate user inputs before sending to blockchain
- Use proper error handling for network issues
- Implement proper access controls in smart contracts
- Regular security audits of smart contracts
2. User Experience
- Provide clear feedback during transaction processing
- Show gas estimates before transaction submission
- Handle network congestion gracefully
- Provide fallback options for users without crypto wallets
3. Performance Optimization
- Use event listeners for real-time updates instead of polling
- Implement proper caching for blockchain data
- Use batch operations when possible
- Optimize smart contract gas usage
Conclusion
Integrating blockchain with React applications opens up numerous possibilities for decentralized applications. By following these patterns and best practices, you can build robust, user-friendly Web3 applications that leverage the power of blockchain technology.
Remember that blockchain development is still evolving, so stay updated with the latest tools, libraries, and best practices in the Web3 ecosystem.
