'Where to load contract to prevent undefined methods when calling contract?
I am trying to run a method from my smart contract located on the ropsten test network but am running into the following error in my getBalance() function:
Unhandled Runtime Error TypeError: Cannot read properties of undefined (reading 'methods')
46 | async function getBalance() {
> 47 | const tempBal = await window.contract.methods.balanceOf(account.data).call()
| ^
48 | setBalance(tempBal)
49 | }
Connecting my contract:
export default function ConnectContract() {
async function loadWeb3() {
if (window.ethereum) {
window.web3 = new Web3(window.ethereum)
window.ethereum.enable()
}
}
async function loadContract() {
const tempContract = await new window.web3.eth.Contract(ABI, ContractAddress)
return tempContract
}
async function load() {
await loadWeb3();
window.contract = await loadContract();
}
load();
}
Currently the functions are being called like so:
export default function Page() {
ConnectContract();
getBalance();
}
Another way I previous called it that did not result in the undefined error was by using an onClick function inside a button to getBalance:
<div><button onClick={() => getBalance}>Get Balance</button></div>
I know calling them like this is causing the getBalance function method to be called before the contract is connected to the website but with the current code, it works after 1-page refresh. But I have not been able to find a solution to make the contract be loaded so the method is defined by the time getBalance is called.
I have tried doing onLoad function and window.load() functions with no success. I have also tried calling my ConnectContract function in the _app.js to load it when the website is launched but that had no success as well.
Solution 1:[1]
You have to call the loadWeb3 function inside useEffect hook. useEffect is used to prepare the state for the component when it is mounted. to keep track of the state, use useState hook
const [web3,setWeb3]=useState('')
const [contract,setContract]=useState('')
async function loadWeb3() {
if (window.ethereum) {
window.ethereum.enable()
setWeb3(new Web3(window.ethereum))
}
}
// [] array means we want this useEffect code run only when the compoenent rerenders
// Since loadWeb3 is async function, call it inside try/catch block
useEffect(()=>{loadWeb3()},[])
Next we you need to prepare contract same way.
async function loadContract() {
const tempContract = await new web3.eth.Contract(ABI, ContractAddress)
setContract(tempContract)
}
You also need to call this in useEffect but this time its dependencies are different. Because in order to get the contract, we are relying on web3
// Since loadContract is async function, call it inside try/catch block
useEffect(()=>{loadContract},[web3])
when component renders, first useEffect will run and set the web3. Since web3 is changed, component will rerender again this useEffect will run. So when your component is mounted, you will have web3 and contract set so you can use those.
Solution 2:[2]
Your issue is in this code right here:
46 | async function getBalance() {
> 47 | const tempBal = await window.contract.methods.balanceOf(account.data).call()
| ^
48 | setBalance(tempBal)
49 | }
My guess is that the window.contract either doesn't exist or isn't getting loaded before this gets called. Perhaps it is either getting called in a serverside place where the window doesn't exist or try wrapping it in an if function so that it checks to make sure it exists before calling it
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Sicarius |
