Best way to listen for events in react-moralis

I think that you have to use enableWeb3 before that, and metamsk should pop up automatically

Currently, Im using the function below to mint nftā€™s and get the confirmed transaction response. It works, and I get the token Id that I need, but I was wanting to use the Moralis apiā€™s so i can get used to them.

This code launches metamask and waits for the confirmed response. How would i do this using useWeb3Contract?

const mintNft = async () => {

        const ethers = Moralis.web3Library
    
        const web3 = await Moralis.enableWeb3({ provider: "web3Auth", chainId: '0x3', clientId:
        "redacted" });
        const signer = web3.getSigner()
        const contract = new ethers.Contract(TOKEN_CONTRACT_ADDRESS, abi, signer)
        
        const transaction = await contract.createItem(uri)
        const tx = await transaction.wait()
        const event = tx.events[0]
        const value = event.args[2]
        const tokenId = value.toNumber()
        
        return tokenId
        
  }

this may be a case that is not handled now by Moralis.executeFunction, you could try with Moralis.executeFunction to see if it works

I see. So, useWeb3Contract doesnt support function calls that require a signed wallet transaction?
I just want to make sure I understand correctly. Thanks for your help

Amazing. Iā€™ll try this out this week. Thanks!

@DappThatApp @PatrickAlphaC on a related note, are you using event listeners to check if user is on the wrong chain etc?
I successfully handled most things like change in account and a case of manual disconnecting metamask from the dapp by using {account} = useMoralis(); which causes re-render anytime account is changed.
But I canā€™t seem to figure out the onChainChanged event listener. If you can copy paste that bit of your code it will help.

1 Like

hey @zubin. the way to handle chain changes you can request metamask to handle chain changes whenever the user clicks on some button to change which uses a metamask request option to change chain by triggering a popup forcing the user to change. on the other hand if a user just decides to change chain then you can use an onChainChange event handler to trigger the popup automatically with useEffect also. the code below shows an example of how to do this for the example of ethereum, ploygon and binance smart chain. this is even more powerful implemnted with web3react. or mroalis if you desire

import { useState, useEffect } from "react";

const networks = {
  polygon: {
    chainId: `0x${Number(137).toString(16)}`,
    chainName: "Polygon Mainnet",
    nativeCurrency: {
      name: "MATIC",
      symbol: "MATIC",
      decimals: 18
    },
    rpcUrls: ["https://polygon-rpc.com/"],
    blockExplorerUrls: ["https://polygonscan.com/"]
  },
  bsc: {
    chainId: `0x${Number(56).toString(16)}`,
    chainName: "Binance Smart Chain Mainnet",
    nativeCurrency: {
      name: "Binance Chain Native Token",
      symbol: "BNB",
      decimals: 18
    },
    rpcUrls: ["https://bsc-dataseed1.binance.org"],
    blockExplorerUrls: ["https://bscscan.com"]
  }
};

const changeNetwork = async ({ networkName, setError }) => {
  try {
    if (!window.ethereum) throw new Error("No crypto wallet found");
    await window.ethereum.request({
      method: "wallet_addEthereumChain",
      params: [
        {
          ...networks[networkName]
        }
      ]
    });
  } catch (err) {
    setError(err.message);
  }
};

export default function App() {
  const [error, setError] = useState();

  const handleNetworkSwitch = async (networkName) => {
    setError();
    await changeNetwork({ networkName, setError });
  };

  const networkChanged = (chainId) => {
    console.log({ chainId });
  };

  useEffect(() => {
    window.ethereum.on("chainChanged", networkChanged);

    return () => {
      window.ethereum.removeListener("chainChanged", networkChanged);
    };
  }, []);

  return (
    <div className="credit-card w-full lg:w-1/2 sm:w-auto shadow-lg mx-auto rounded-xl bg-white">
      <main className="mt-4 p-4">
        <h1 className="text-xl font-semibold text-gray-700 text-center">
          Force MetaMask network
        </h1>
        <div className="mt-4">
          <button
            onClick={() => handleNetworkSwitch("polygon")}
            className="mt-2 mb-2 btn btn-primary submit-button focus:ring focus:outline-none w-full"
          >
            Switch to Polygon
          </button>
          <button
            onClick={() => handleNetworkSwitch("bsc")}
            className="mt-2 mb-2 bg-warning border-warning btn submit-button focus:ring focus:outline-none w-full"
          >
            Switch to BSC
          </button>
         
        </div>
      </main>
    </div>
  );
}


the above example is basic and only supports thos two chains. to make it more robust you should integrate this web3react or moralis (which you are jsing) and use this hook to get the current chain. something like

const { chainId } = useWebReact()

or for your case

const { cahinId } = useMoralis

if you then, in the useffect with the chainChain event listener add the dependancy of the chainId then evrry time you switch networks the popup will automatically show, that is if you call the handleNetworkChange function inside the useEffect.

2 Likes

@mcgrane5 thanks so much for the detailed reply. I will try implementing this way!

@mcgrane5 Hereā€™s what I did:

  1. In my App.js I added an if statement to an existing useEffect in order to change the network immediately on Metamask login.
useEffect(() => {
    const connectorId = window.localStorage.getItem("connectorId");
    if (isAuthenticated && !isWeb3Enabled && !isWeb3EnableLoading){
        enableWeb3({ provider: connectorId });}
    if (chainId != '0xa86a') {
      switchNetwork("0xa86a")
    }

So far so good.
2. In order to catch the edge case of user absent mindedly switching network again after login (perhaps while working on another tab with Ethereum), I added a new useEffect with an event listener as you suggested.

useEffect(()=>{

    if(chainId != '0xa86a'){

      const unsubscribed = Moralis.onChainChanged((chain)=>{
        if (window.confirm(`You're on ${chain}! Click OK to switch to Avalanche!`)) {
          switchNetwork("0xa86a")
        } else {
          alert("You're on the wrong network & will result in loss of funds due to failed transaction! Switch to Avalance C-chain manually in your Metamask Wallet!");
        }
        unsubscribed();
    }); }
  }, [chain])

Trouble is, this extra useEffect keeps getting triggered even if I am already on Avax. Iā€™ve tried all kinds of permutations with the If statement. Any idea what Iā€™m doing wrong? image

Edit:
I solved it by removing the above useEffect in #2 and including chain in the array of the original useEffect.

useEffect(() => {
    const connectorId = window.localStorage.getItem("connectorId");
    if (isAuthenticated && !isWeb3Enabled && !isWeb3EnableLoading){
        enableWeb3({ provider: connectorId });}
    if (chainId != '0xa86a') {
      if (window.confirm(`You're on the wrong network! Click OK to switch to Avalanche C-chain!`)) {
              switchNetwork("0xa86a")
            } else {
              alert("You're on the wrong network & will result in loss of funds due to failed transaction! Switch to Avalance C-chain manually in your Metamask Wallet!");
           }
    }  
  }, [isAuthenticated, isWeb3Enabled, chain]);

This way if user is not on Avax it prompts and then changes the chain to Avax.

2 Likes

useWeb3Contract({
// other options
onSuccess: (tx) => {},
});

This fires the moment you hit the confirm button on Metamask. Itā€™s pretty much useless if you are trying to use it as a callback for when a tx has been confirmed successfully.

Hi, Patrick please tell me you tried this or found another way?

I cannot for the life of me figure out how to listen or track a successful tx confirmation using react-moralis.
Iā€™m using the useWeb3ExecuteFunction hook to mint my nfts but no way to figure out whether it got successfully minted.

Yep! I found out you have to add the onSuccess when you call the function. For example, if this is where you define your function:

    const {
        runContractFunction: enterRaffle,
        data: enterTxResponse,
        isLoading,
        isFetching,
    } = useWeb3Contract({
        abi: abi,
        contractAddress: raffleAddress,
        functionName: "enterRaffle",
        msgValue: entranceFee,
        params: {},
    })

Then youā€™d add your success function like so:

                    <button
                        className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded ml-auto"
                        onClick={async () =>
                            await enterRaffle({
                                onSuccess: handleSuccess,
                            })
                        }
                        disabled={isLoading || isFetching}
                    >

And here is my func:

    const handleSuccess = async (tx) => {
        await tx.wait(1)
        updateUIValues()
        handleNewNotification(tx)
    }
2 Likes

Wow dude, it works perfectly. Thank you very much!

Thanks to the Moralis team for helping me find the answer!!!

1 Like

@orangemat enterRaffle is the contract function name right? Shouldnā€™t it be async()=> await fetch and not enterRaffle? Unless youā€™ve declared fetch: enterRaffle in the useWebExecuteFunction?

Fetch is equivalent to ā€œrunContractFunctionā€ here. Also, you need to use the useWeb3Contract hook instead of the useWeb3ExecuteFunction one.

2 Likes

Both are basically the same. UseWeb3ExecuteFunction is working well for me. Itā€™s just changing/deactivating the button after confirmation of txns that is the problem. And keeping it deactivated on a page refresh. Difference between `useWeb3ExecuteFunction`, `useApiContract`, `executeFunction`, `useWeb3Contract`, and `Moralis.executeFunction`

1 Like

Ok, I got it so far to run funcitons depending onSuccess, onComplete or onError, but the values
of given data like error are undefined. I guess you did the trick with te tx.wait but I cant get it working to assing the useWeb3Contract with a tx and therefore wait.
Could you please tell me how you defined your tx in this example?
I mean with this code the handleError is called, but the if (error) statement results always in the else because error is undefined

const { runContractFunction: enterRaffle, data: enterTxResponse, error, isLoading, isFetching } = useWeb3Contract({

        abi: abi,

        contractAddress: contractAddress,

        functionName: "addAddress",

        params: {

            _addressToBeAdded: account

        },

    }

    );

    const InteractContract = async () => {

        await enterRaffle(

            {

                onSuccess: handleSuccess,

                onComplete: handleComplete,

                //onError: () => { handleError(JSON.stringify(error)) },

                onError: handleError

            }

        )

        if (error) {

            //error occured

            alert(error?.message.substring(error?.message.search('message') + 10, error?.message.search('data') - 3))

        } else {

            alert("success")

        }

    }
1 Like

Hello michimich^^. Just kidding, will answer my own question, because I just figured it out.

for listening on the tx and therefure execute a wait it could be done like this:

const tx = await enterRaffle(
            {
                onSuccess: (tx) => tx.wait(1).then(handleSuccess),
                onComplete: handleComplete,
                //             //onError: () => { handleError(JSON.stringify(error)) },
                onError: handleError,
            }
        )

so then the handleSuccess is only called if the transaction was successfull. This works fine for me, will ad the same on the other handles as well.

Also I figured out, that a require in my contract can lead to a instant response with the error message, will make that clear with some screenshots if I have them.
Hope this could help others

So onSuccess is when the transaction goes through, which is nice, what about when the transaction gets at least 1 block confirmation? Does the API work for that too?