Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Lesson 10 - "A Challenge To You" (automatically polling for Raffle events) #5631

vvinteresting started this conversation in Show and tell
Discussion options

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:

https://github.com/vvinteresting/fcc-solidity/blob/8b7bcad861264fcc2c6c325f8af435d0a9bf97a6/nextjs-lottery/components/LotteryEntrance.tsx

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>
 )
}
You must be logged in to vote

Replies: 0 comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
1 participant

AltStyle によって変換されたページ (->オリジナル) /