Recommended resources
The freely available resources this lesson will direct you to.
- Hello World Ethereum React Dapp Tutorial. React, Ethers.js, & MetaMask.www.youtube.com
- Ethereum Dapp: Connect React app to MetaMask Tutorialwww.youtube.com
- Into the Ether: Wallet Integration on Ethereum Using Ethers.js v6mark-okeeffe-11887.medium.com
- Creating a “Connect Wallet” Button Using ethers.js v6 in ...medium.com
Hello! Welcome to the next lesson in our dApp frontend module.
In the last lesson, we successfully established a foundational connection between our React application and the Ethereum network. We created a "Connect Wallet" button, fetched the user's address, and even made the connection robust by listening for account changes.
Today, we'll build on that foundation to implement more sophisticated wallet connection logic. Our goal is to move from a basic proof-of-concept to a more practical and user-friendly implementation. We will refactor our logic into a dedicated component, fetch and display the user's ETH balance, and ensure our application gracefully handles events like the user switching accounts or networks within MetaMask. This is a key step in creating a polished user experience for any dApp.
1. The Need for Component-Based Architecture
In our previous lesson, we put all the connection logic directly into App.tsx. While this worked, in a real-world application, it's better to organize code into smaller, reusable components. Given your background in backend development, you can think of this as applying the Single Responsibility Principle: we'll create one component whose sole job is to manage the wallet connection and display its state.
This approach keeps our main App.tsx file clean and makes the wallet connection logic easier to manage, debug, and even reuse elsewhere. We'll create a WalletCard component that will handle:
- Displaying a "Connect Wallet" button.
- Prompting the user to connect with MetaMask.
- Displaying the connected account address and ETH balance.
- Dynamically updating when the user changes their account or network in MetaMask.

2. Building the WalletCard Component: A Video Guide
We're going to follow a video tutorial that walks through building a component very similar to what we need. It will demonstrate how to structure the state, handle the connection, fetch the balance, and listen for important wallet events.
This video uses JavaScript, but the principles are identical. We will adapt the final code to our TypeScript project afterwards.
Ethereum Dapp: Connect React app to MetaMask Tutorial
The video 'Ethereum Dapp: Connect React app to MetaMask Tutorial' by Michael Couch provides an excellent walkthrough for building a self-contained wallet connection component. We'll watch it in segments.
We will break down this video into key parts. I will guide you on which segments to watch and then we'll discuss the concepts.
Step 2.1: Setting up the Component and Connection Logic
First, let's see how to structure the React component and write the core function that handles the wallet connection request.
Ethereum Dapp: Connect React app to MetaMask Tutorial
Watch this segment to understand how to set up the component's UI and state, and how to implement the connectWalletHandler function.
Please watch from 03:01 to 09:46. Focus on: How useState is used to manage different pieces of information (account, balance, error messages). The logic inside connectWalletHandler, specifically how it checks for window.ethereum and uses eth_requestAccounts to prompt MetaMask.
The video demonstrates the core connection flow:
- Check for MetaMask: The function first checks if
window.ethereumexists. If not, it sets an error message prompting the user to install MetaMask. - Request Accounts: It then calls
window.ethereum.request({ method: 'eth_requestAccounts' }). This is the JSON-RPC method that asks the user to connect their wallet. It returns a promise that resolves with an array of the user's account addresses. - Update State: Once the account is received, a handler function (
accountChangedHandlerin the video) is called to update the component's state with the new address, causing the UI to re-render and display it.
Step 2.2: Fetching the User's Balance
Just having the address is useful, but users expect to see their balance. Let's look at how to query the blockchain for this information.
Ethereum Dapp: Connect React app to MetaMask Tutorial
This next clip shows how to fetch the connected account's ETH balance and format it for display.
Now, watch from 09:46 to 12:23. Pay close attention to: The eth_getBalance RPC method and its parameters. The format of the raw balance returned from the blockchain. The use of ethers.utils.formatEther to convert the balance into a human-readable format.
Fetching the balance involves two key steps:
- Request Balance: We use another RPC method,
eth_getBalance, passing the user's address and the block tag'latest'to get the most recent balance. - Format Balance: The balance is returned as a large integer string representing the amount in Wei (the smallest denomination of Ether, Wei = 1 ETH). This is not user-friendly. The
etherslibrary provides a handy utility,ethers.utils.formatEther(), which correctly converts this Wei value into an ETH value as a string, which we can then display.
Step 2.3: Handling Wallet Events
A robust dApp must react when the user interacts with their wallet. The previous lesson introduced this, and this video reinforces the pattern in a clean, component-based way.
Ethereum Dapp: Connect React app to MetaMask Tutorial
Finally, let's see how to make our component dynamically respond to the user switching accounts or networks in MetaMask.
Watch the final segment from 12:23 to 17:00. This covers listening for the accountsChanged and chainChanged events.
The key takeaways here are the two event listeners:
window.ethereum.on('accountsChanged', ...): This event fires when the user switches their active account. The handler function receives the new account address, allowing us to update the UI and re-fetch the new account's balance automatically.window.ethereum.on('chainChanged', ...): This event fires when the user switches networks (e.g., from Sepolia to Mainnet). The safest and most common way to handle this is to force a page reload (window.location.reload()). This ensures your dApp re-initializes its state and connection, preventing it from trying to interact with the wrong network.
3. Implementing the WalletCard in TypeScript
Now, let's translate the concepts from the video into a complete WalletCard.tsx component for our project.
- Create a new file in your
src/directory namedWalletCard.tsx. - Paste the following code into the new file. This code adapts the video's logic to TypeScript and uses the
useEffecthook to manage the event listeners, as we learned in the previous lesson.
// src/WalletCard.tsx
import { useState, useEffect } from 'react';
import { ethers } from 'ethers';
import './WalletCard.css';
const WalletCard = () => {
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [account, setAccount] = useState<string | null>(null);
const [balance, setBalance] = useState<string | null>(null);
useEffect(() => {
// Listener for account changes
const accountWasChanged = (accounts: string[]) => {
if (accounts.length > 0) {
setAccount(accounts[0]);
getAccountBalance(accounts[0]);
} else {
setAccount(null);
setBalance(null);
}
}
// Listener for chain changes
const chainWasChanged = () => {
// A simple reload is a robust way to handle network changes
window.location.reload();
}
if (window.ethereum) {
// Check for existing connection on page load
connectHandler();
window.ethereum.on('accountsChanged', accountWasChanged);
window.ethereum.on('chainChanged', chainWasChanged);
}
// Cleanup function to remove listeners
return () => {
if (window.ethereum) {
window.ethereum.removeListener('accountsChanged', accountWasChanged);
window.ethereum.removeListener('chainChanged', chainWasChanged);
}
};
}, []);
const connectHandler = async () => {
if (window.ethereum) {
try {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
if (accounts.length > 0) {
setAccount(accounts[0]);
getAccountBalance(accounts[0]);
}
} catch (err: any) {
setErrorMessage(err.message);
}
} else {
setErrorMessage('Please install MetaMask to use this dApp.');
}
};
const getAccountBalance = async (accountAddress: string) => {
if (window.ethereum) {
try {
const provider = new ethers.BrowserProvider(window.ethereum);
const accountBalance = await provider.getBalance(accountAddress);
setBalance(ethers.formatEther(accountBalance));
} catch (err: any) {
setErrorMessage(err.message);
}
}
};
return (
<div className='wallet-card'>
<h3 className='wallet-card-title'>Wallet Connection</h3>
{account ? (
<div>
<p><strong>Address:</strong> {`${account.substring(0, 6)}...${account.substring(account.length - 4)}`}</p>
<p><strong>Balance:</strong> {balance ? `${parseFloat(balance).toFixed(4)} ETH` : 'Loading...'}</p>
</div>
) : (
<button className='connect-wallet-button' onClick={connectHandler}>
Connect Wallet
</button>
)}
{errorMessage && <p className='error-message'>{errorMessage}</p>}
</div>
);
};
export default WalletCard;
- Create a corresponding CSS file
src/WalletCard.cssfor styling:
/* src/WalletCard.css */
.wallet-card {
border: 1px solid #ccc;
border-radius: 8px;
padding: 20px;
width: 350px;
background-color: #f9f9f9;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.wallet-card-title {
margin-top: 0;
color: #333;
}
.connect-wallet-button {
width: 100%;
padding: 12px 24px;
font-size: 16px;
font-weight: bold;
color: white;
background-color: #007bff;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s;
}
.connect-wallet-button:hover {
background-color: #0056b3;
}
.error-message {
color: #dc3545;
margin-top: 15px;
word-wrap: break-word;
}
- Finally, update your
src/App.tsxto use this new component, cleaning it up significantly:
// src/App.tsx
import './App.css';
import WalletCard from './WalletCard';
function App() {
return (
<div className="App">
<header className="App-header">
<h1>My Token dApp</h1>
<WalletCard />
</header>
</div>
);
}
export default App;
Now, run your app with npm run dev. You should see the new WalletCard component. It will prompt you to connect, and once connected, it will display your truncated address and formatted ETH balance. Try switching accounts in MetaMask—the UI should update automatically!
Test your understanding!
In the last lesson, we used provider.getSigner() to prompt the user to connect. In this lesson's code and video, window.ethereum.request({ method: 'eth_requestAccounts' }) is used. Both seem to achieve the same result of bringing up the MetaMask connection prompt. What is the key difference between these two approaches?
Show answer
The main difference lies in their level of abstraction.
-
window.ethereum.request({ method: 'eth_requestAccounts' })is a direct call to the low-level JSON-RPC API that MetaMask injects into the browser (window.ethereum). Its primary job is to request account access, and it returns an array of account strings. -
provider.getSigner()is a higher-level abstraction provided by theethers.jslibrary. When you call it,ethersinternally useseth_requestAccountsto get the account, but it doesn't just return the address string. It returns aSignerobject. ThisSigneris already configured and ready to be used for "write" operations, like signing and sending transactions.
In short: eth_requestAccounts gets you the account address, while getSigner() gets you an object that represents the account and can perform actions on its behalf. We'll be using the Signer object extensively in future lessons to interact with our smart contracts.
Conclusion
Excellent work! You've taken our basic wallet connection and transformed it into a robust, reusable, and user-friendly component. This is a crucial pattern for building clean and maintainable dApps.
Key Takeaways:
- Encapsulating logic into dedicated React components (like
WalletCard) improves code organization and reusability. - You can fetch a user's native token balance using
provider.getBalance()(or theeth_getBalanceRPC method). - Balances returned from the blockchain are in Wei and must be formatted using utilities like
ethers.formatEther()for display. - Combining
useStateanduseEffectwith event listeners foraccountsChangedandchainChangedcreates a dApp that feels responsive and reliable to the end-user.
Preview of the Next Lesson:
Our connection logic is now solid. The next step is to start interacting with our own smart contract. In the next lesson, we will read and display data from your ERC-20 token contract. We'll learn how to create an ethers.js contract instance and use it to fetch and display your token's name, symbol, and total supply right in our dApp.