Best way to listen for events in react-moralis

@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?

Added a new thread here: Best way to wait for a transaction to finish

Oh Iā€™m idiot, you do await tx.wait(1).

Exactly what I said above XD

FYI for anyone it might be helpful to (as things are often use case dependent) - you can also just chain the event

eg.

runContractFunction(params)
  .then(data => {
      //do something with data
  }).catch(err =>{ 
      //handle error
  })
1 Like

hi, please the ā€œenterTxResponseā€ in your code references to what if i was to use usdt has my contract?

Hello there,

I think I just linked a variable on my side to work with the the given data then (man this code doesnĀ“t look pretty good).
In general (Patrick mentioned this at the beginning of this thread):

data: The response of running the contract function

I canĀ“t remember exactly, because I changed it to be more clear. After the mint() function ran I go on and handle the result with either an error message or a success messag. This is what I came up with (if this helps you):

const { runContractFunction: mint } = useWeb3Contract({
        abi: abi,
        contractAddress: process.env.REACT_APP_NFT_CONTRACT_ADDRESS,
        functionName: "mint"
    }
    );

//calling the mint function by using the useWeb3Contract()
await mint({
        params: options,
        onSuccess: (tx) => (handleSuccess(tx)),
        onError: (tx) => (handleError(tx)),
    });

//handle success

const handleSuccess = async (tx) => {
    await tx.wait(1);
    console.log("success entered")
    console.log("tx", tx);
    return ['success', tx];
}


//deal with an occured error
const handleError = async (tx) => {
    var createdErrorMessage;
    console.log("tx from interaction", tx);
    if (tx.error !== undefined) {
        createdErrorMessage = tx.error.message;
        console.log("filtered error message", createdErrorMessage);
    }
    else if (tx.message !== undefined) {
        //tx not fired, could be user cancel transaction
        createdErrorMessage = tx.message;
        console.log("tx message", tx.createdErrorMessage);
    }
    else {
        createdErrorMessage = "undefined error occured";
    }
    console.log('error', createdErrorMessage, "Error");
    return createdErrorMessage;
}

Greetings