-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Lesson 10 - "A Challenge To You" (automatically polling for Raffle events) #5631
-
I had a very hard time figuring out how to make this in a performant way, so I thought I would share how I made this work locally.
instantiate provider in state. This was crucial for me, because otherwise provider kept getting re-defined on re-render, which would occur constantly due to updates to other elements in the component. This would result in looping polling requests to the chain and was hugely resource intensive.
const [ provider, setProvider ] = useState(new ethers.providers.JsonRpcProvider(process.env.RPC_URL!));
instantiate the contract:
const Lottery = new ethers.Contract(lotteryAddress, abi as ContractInterface, provider);
listen for the events, and update state when appropriate:
useEffect(() => {
Lottery.once("LotteryEntered", async () => {
console.log("--- Lottery Entered! ---")
await fetchUI();
});
Lottery.once("WinnerFound", async () => {
console.log("--- Winner Found! ---")
await fetchUI();
});
}, [Lottery]);
There are still some issues: Events are sometimes received in duplicate, and events from the past also fire Lottery.once() sometimes (this is a known issue with Ethers v5, see here: ethers-io/ethers.js#2353). Additionally, fetchUI sets state a dozen times in a row, despite first checking if the previous value and the new value differ before setting. This must be a quirk with React's state that I don't yet understand.
Regardless, this works to update the UI based on contract events alone, instead of relying on user interaction to trigger them. Here's my code:
import { useWeb3Contract} from "react-moralis"
import { abi, address } from "../constants";
import { useMoralis } from "react-moralis";
import { useEffect, useState } from "react";
import { ethers, ContractTransaction, ContractInterface } from "ethers";
import { useNotification, ConnectButton } from "web3uikit";
export default function LotteryEntrance() {
interface addressType {
[key: number]: string[];
}
const addressObj: addressType = address;
const { chainId: chainIdHex, isWeb3Enabled } = useMoralis();
const chainId: number = parseInt(chainIdHex as string);
const lotteryAddress: string = chainId in address ? addressObj[chainId][0] : "0x0";
const [ values, setValues ] = useState({
entranceFee: "0",
numPlayers: "0",
recentWinner: "0"
});
// keeping provider in state drastically reduced weird rerenders and duplicate calls.
const [ provider, setProvider ] = useState(new ethers.providers.JsonRpcProvider(process.env.RPC_URL!));
// 7 seconds - fixing the polling interval also seemed to help a lot to reduce duplicate calls.
provider.pollingInterval = 7000;
const Lottery = new ethers.Contract(lotteryAddress, abi as ContractInterface, provider);
const dispatch = useNotification();
const { runContractFunction: enterLottery, isLoading, isFetching } = useWeb3Contract({
abi: abi,
contractAddress: lotteryAddress, // specify network id
functionName: "enterLottery",
params: {},
msgValue: values.entranceFee
});
const { runContractFunction: getEntranceFee } = useWeb3Contract({
abi: abi,
contractAddress: lotteryAddress, // specify network id
functionName: "getEntranceFee",
params: {}
});
const { runContractFunction: getNumberOfPlayers } = useWeb3Contract({
abi: abi,
contractAddress: lotteryAddress, // specify network id
functionName: "getNumberOfPlayers",
params: {}
});
const { runContractFunction: getRecentWinner } = useWeb3Contract({
abi: abi,
contractAddress: lotteryAddress, // specify network id
functionName: "getRecentWinner",
params: {}
});
// functions
const fetchUI = async () => {
let entranceFeeFromCall: any = await getEntranceFee();
let numPlayersFromCall: any = await getNumberOfPlayers();
let recentWinnerFromCall: any = await getRecentWinner();
let entranceFeeString: string = entranceFeeFromCall.toString();
let numPlayersString: string = numPlayersFromCall.toString()
let recentWinnerString: string = recentWinnerFromCall;
console.log(entranceFeeString, values.entranceFee);
console.log(numPlayersString, values.numPlayers);
if (entranceFeeString != values.entranceFee || numPlayersString != values.numPlayers) {
console.log("Updating store...");
// this was meant to reduce rerenders, but it seems to work the same as before. oh well!
setValues({
entranceFee: entranceFeeString,
numPlayers: numPlayersString,
recentWinner: recentWinnerString
});
}
return;
};
const handleSuccess = async (tx: ContractTransaction) => {
await tx.wait(1);
handleNewNotification();
};
const handleNewNotification = () => {
dispatch({
type: "info",
message: "tx complete!",
title: "tx notification",
position: "topR",
});
};
// effects
useEffect(() => {
if(isWeb3Enabled) {
console.log("enabled!");
fetchUI();
}
}, [isWeb3Enabled]);
// tbh i dont know if passing Lottery as a dependency makes a difference here
useEffect(() => {
Lottery.once("LotteryEntered", async () => {
console.log("--- Lottery Entered! ---")
await fetchUI();
});
Lottery.once("WinnerFound", async () => {
console.log("--- Winner Found! ---")
await fetchUI();
});
}, [Lottery]);
return (
<div className={`flex flex-col min-w-full min-h-full`}>
<div className={`flex flex-col w-full justify-between items-center border-b-2 border-gray-600 pb-4 mb-8 md:items-start md:flex-row`}>
<h1 className="text-white text-xl m-2 md:m-0">Hi, from Lottery Entrance</h1>
<ConnectButton moralisAuth={false} className={`m-2 md:m-0`} />
</div>
{
lotteryAddress
?
<div className={`flex flex-col text-center items-center`}>
<button
className={`rounded-lg p-2 bg-slate-100 text-black mr-2 max-w-fit mb-8 hover:bg-slate-300 disabled:bg-white disabled:text-gray-300`}
onClick={
async () => {
await enterLottery({
onSuccess: (tx) => handleSuccess(tx as ContractTransaction),
onError: (error) => console.log(error)
})
}
}
disabled={isLoading || isFetching}>
{isLoading || isFetching ? <div className={`animate-spin spinner-border h-4 w-4 p-2 border-b-2 rounded-full`} /> : <div>Enter Lottery</div>}
</button>
<strong>Entrance fee:</strong>
<p>{ethers.utils.formatUnits(values.entranceFee, 'ether')} ETH</p>
<br/>
<strong>Number of players:</strong>
<p>{ values.numPlayers }</p>
<br/>
<strong>Recent winner:</strong>
<p>{ values.recentWinner }</p>
</div>
:
<div>No lottery address detected</div>
}
</div>
)
}
Beta Was this translation helpful? Give feedback.