πŸ‘‚ Listening to Events: Real-Time dApp Updates

Build dApps that react to contract events using Web3.js and ethers.js

←
Previous Section
Indexed Parameters

πŸ‘‚ 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

Easy

Real-time event listeners with filters

βœ… Pros
  • β€’ Real-time updates
  • β€’ Simple API
  • β€’ Filter support
⚠️ Cons
  • β€’ WebSocket connection
  • β€’ Memory overhead
Best Use Case
Live dApp updates, notifications

🎯 Interactive: Live Event Stream Simulator

Simulate real-time event listening:

Event Stream

Click "Start Listening" to begin receiving events...
πŸ’‘ 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

🎯
Filter Strategically

Use indexed parameters to filter events client-side. Only listen for what you need.

🧹
Clean Up Listeners

Always remove listeners when components unmount to prevent memory leaks.

πŸ”„
Handle Reconnections

WebSocket connections can drop. Implement reconnection logic with exponential backoff.

πŸ’Ύ
Cache Historical Data

Don't refetch old events every time. Use The Graph or local cache for historical queries.

⚠️ Common Pitfalls

1️⃣
Missing confirmations: Events fire immediately but can be reorged. Wait for block confirmations in production.
2️⃣
Memory leaks: Forgetting to remove listeners causes performance degradation over time.
3️⃣
Over-polling: Polling too frequently wastes RPC calls and may hit rate limits.
4️⃣
No error handling: Connection failures will crash your app. Always handle errors gracefully.