π Listening to Events: Real-Time dApp Updates
Build dApps that react to contract events using Web3.js and ethers.js
Your Progress
0 / 5 completedπ Listening to Events in dApps
Event listeners are the backbone of reactive dApps. They enable real-time UI updates, notifications, and seamless user experiences without constant blockchain polling.
π― Interactive: Listening Methods Comparison
Explore 4 approaches to event listening:
ethers.js Listeners
EasyReal-time event listeners with filters
- β’ Real-time updates
- β’ Simple API
- β’ Filter support
- β’ WebSocket connection
- β’ Memory overhead
π― Interactive: Live Event Stream Simulator
Simulate real-time event listening:
Event Stream
π‘ What's Happening:
Your dApp would listen for events like this in real-time. When users transfer tokens, approve spending, or interact with your contract, events trigger UI updates instantlyβno polling required!
Implementation Examples
β‘ethers.js Event Listener
import { ethers } from 'ethers';
// Setup provider and contract
const provider = new ethers.providers.WebSocketProvider(RPC_URL);
const contract = new ethers.Contract(ADDRESS, ABI, provider);
// Listen for Transfer events
contract.on("Transfer", (from, to, amount, event) => {
console.log(`${from} sent ${amount} to ${to}`);
// Update UI
updateBalance(to, amount);
showNotification(`Received ${amount} tokens!`);
});
// Filter: Only transfers TO specific address
const filter = contract.filters.Transfer(null, myAddress);
contract.on(filter, (from, to, amount) => {
console.log(`You received ${amount} from ${from}`);
});
// Clean up listener when component unmounts
return () => {
contract.removeAllListeners("Transfer");
};πweb3.js Subscription
import Web3 from 'web3';
const web3 = new Web3(new Web3.providers.WebsocketProvider(WSS_URL));
const contract = new web3.eth.Contract(ABI, ADDRESS);
// Subscribe to events
const subscription = contract.events.Transfer({
filter: { to: myAddress }, // Only transfers to me
fromBlock: 'latest'
})
.on('data', (event) => {
const { from, to, value } = event.returnValues;
console.log(`Received ${value} from ${from}`);
// Update UI state
dispatch(addTransaction({ from, to, value }));
})
.on('error', (error) => {
console.error('Event error:', error);
});
// Unsubscribe when done
subscription.unsubscribe();πPolling with getLogs
import { ethers } from 'ethers';
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const contract = new ethers.Contract(ADDRESS, ABI, provider);
// Poll every 15 seconds
let lastBlock = await provider.getBlockNumber();
setInterval(async () => {
const currentBlock = await provider.getBlockNumber();
// Query events from last poll to now
const filter = contract.filters.Transfer(null, myAddress);
const events = await contract.queryFilter(
filter,
lastBlock + 1,
currentBlock
);
// Process new events
events.forEach((event) => {
const { from, to, amount } = event.args;
console.log(`New transfer: ${from} β ${to}: ${amount}`);
updateUI(event);
});
lastBlock = currentBlock;
}, 15000);πThe Graph Subgraph Query
import { request, gql } from 'graphql-request';
const query = gql`
{
transfers(
where: { to: "${myAddress}" }
orderBy: blockNumber
orderDirection: desc
first: 10
) {
id
from
to
amount
blockNumber
timestamp
}
}
`;
const data = await request(SUBGRAPH_URL, query);
// Process historical transfers
data.transfers.forEach((transfer) => {
console.log(`
From: ${transfer.from}
To: ${transfer.to}
Amount: ${transfer.amount}
Block: ${transfer.blockNumber}
`);
});Common Patterns & Best Practices
Use indexed parameters to filter events client-side. Only listen for what you need.
Always remove listeners when components unmount to prevent memory leaks.
WebSocket connections can drop. Implement reconnection logic with exponential backoff.
Don't refetch old events every time. Use The Graph or local cache for historical queries.