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 :
However, everything usually works after I refresh the page
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.
Best Answer
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):
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.