2

StackShare question

Hello, I am having a problem with connecting to Metamask dynamically. I created a voting website using React.js and Next.js, and was using UseEffect() to connect to Metamask. Currently, when I navigate to a page with its link or make any changes to a one, I fail to connect to my smart contract, although I can connect when I refresh manually. I believe that this is due to Next.js preloading data.

For instance, this is what I see when navigating to index.js : enter image description here

However, everything usually works after I refresh the page

enter image description here Here is a basic implementation of my index.js

  useEffect(() => {// get web3
    async function initWeb3() {
      const web3Instance = await getWeb3();
      setWeb3(web3Instance)
    }
    initWeb3();
  },[]);

  useEffect(() => {// get Factory contract
    async function setup() {
      if(web3 == "") {
        console.log('unable to get factory')
        return;
      }
      try {
        // Get the contract instance.
        const networkId = await web3.eth.net.getId();
        const deployedNetwork = VoteFactoryContract.networks[networkId];
        const instance = new web3.eth.Contract(
          VoteFactoryContract.abi,
          deployedNetwork && deployedNetwork.address,
        );
        setContract(instance);// Set web3, accounts, and contract to the state, and then proceed with an
      } catch (error) {
        // Catch any errors for any of the above operations.
        alert(
          `Failed to load web3, accounts, or contract. Check console for details.`,
);
        console.error(error);
      }
    }
      setup();
  },[web3]);

  useEffect(()=> {//display available votes addresses
    var displayVotes = async () => {
      if(contract == ''){
        return;
      }
      const response = await contract.methods.getDeployedVotes().call();
      // Update state with the result.
      setVotesAddresses(response);
    };
    displayVotes();
  },[contract]);

getWeb3() looks like this:

import Web3 from "web3";

const getWeb3 = () =>
  new Promise((resolve, reject) => {
    // Wait for loading completion to avoid race conditions with web3 injection timing.
    window.addEventListener("load", async () => {
      // Modern dapp browsers...
      if (window.ethereum) {
        const web3 = new Web3(window.ethereum);
        try {
          // Request account access if needed
          await window.eth_requestAccounts;
          // Acccounts now exposed
          resolve(web3);
        } catch (error) {
          reject(error);
        }
      }
      // Legacy dapp browsers...
      else if (window.web3) {
        // Use Mist/MetaMask's provider.
        const web3 = window.web3;
        console.log("Injected web3 detected.");
        resolve(web3);
      }
      // Fallback to localhost; use dev console port by default...
      else {
        const provider = new Web3.providers.HttpProvider(
          "http://127.0.0.1:8545"
        );
        const web3 = new Web3(provider);
        console.log("No web3 instance injected, using Local web3.");
        resolve(web3);
      }
    });
  });

export default getWeb3;

Finally, my contract code is as follows:

pragma solidity ^0.7.4;
//"SPDX-License-Identifier: UNLICENSED"

import "./Vote.sol";
 
contract VoteFactory{
    address[] public deployedVotes;
    
    function createVote(uint typeOf) public{
        address newVote = address(new Vote(msg.sender, typeOf));
        deployedVotes.push(newVote);
    }

    function getDeployedVotes() public view returns (address[] memory) {
        return deployedVotes;
    }
    
}

I have spent endless hours trying to debug this, and although I feel like I know what the error is, I cannot find a solution.

2 Answers 2

0

This is probably because you are establishing the web3 connection only in your index.js; therefore, it is not accessible when you navigate into other pages.

You can either use Redux to have global variables accessible from any page, or share data between components through properties.

** UPDATE: **

In case it helps, this is the way I normally connect to web3 through React (in Typescript):

React.useEffect(() => {
    const init = async () => {
        await getWeb3(true);
    }
    init();
}, []);

const getWeb3 = async (isFirstLoad: boolean) => {
    try {
        let web3: Web3;
        if (window.ethereum) {
            web3 = new Web3(window.ethereum);
            // Ask User permission to connect to Metamask
            if (!isFirstLoad) {
                try {
                    await window.ethereum.enable();
                } catch (err) {
                    console.log('Transaction rejected by user:', err)
                };
            };
        } else if (window.web3) {
            web3 = new Web3(window.web3.currentProvider);
        } else {
            window.alert('Non-Ethereum browser detected. Please install MetaMask plugin');
            return;
        };

        dispatch(setWeb3(web3));  // Update web3 into Redux state

        // ...
    } catch (err) {
        console.log('Error in Web3.tsx -> getWeb3(): ', err);
    };
};

The variable isFirstLoad prevents asking the User to reconnect to your site if he/she was already connected.

I had some issues when using window.addEventListener(), so perhaps you can try without it.

3
  • Thank you for the response, but I do not believe this is the problem, as I am unable to connect directly to Web3 when I navigate from another page to index.js as well. Furthermore, all my pages have the same code for connecting to Web3.
    – TheWang
    Commented Feb 26, 2021 at 2:47
  • I updated my answer with a code example. I had issues when doing it with window.addEventListener(), so perhaps you can try without it. By the way, you don't need to connect to web3 in every page: that's why it is better to use Redux or a way to share the web3 variable across the different pages. Commented Feb 26, 2021 at 5:26
  • This is it! I matched my web3 file to yours, and the problem was indeed window.addEventListener(), thank you so much!
    – TheWang
    Commented Feb 26, 2021 at 20:22
0

Metamask injects web3 in old versions and ethereum in new versions. so it is easier to detect provider with @metamask/detect-provider

import detectEthereumProvider from '@metamask/detect-provider'

const provider = await detectEthereumProvider()

You can use this logic either in useEffect or in implementing a submit handler

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.