Home > OS >  React router component calls function multiple times
React router component calls function multiple times

Time:10-01

I am trying to learn blockchain development, so I started to learn Solidity a few weeks ago and I had to create a front-end application for my contract I made with React, which I also don't know.

So, I have read documents and watched tutorials to make use of web3 libraries and some page transitions. Now I can navigate between pages in my application, but when I route to a specific page, my functions get called multiple times.

This is my index.js which gets loaded every time I run the application. I have set my routes like so.

index.js (I don't have app.js and use index.js like app.js)

import React from 'react';
import ReactDOM from 'react-dom';
import './style.css';
import Web3 from 'web3'
import { ConnectPage } from './ConnectPage';
import { MintPage } from './MintPage';
import { AllCryptonauts } from './AllCryptonauts';
import { MyCryptonauts } from './MyCryptonauts';
import { NotFound } from './NotFound';
import { BrowserRouter, Route, Switch } from 'react-router-dom'

function App() {
  if (window.ethereum) {
    window.web3 = new Web3(window.ethereum)
    window.ethereum.enable()
  } else {
    alert("Could not detect MetaMask. Please install MetaMask before proceeding!");
  }

  return (
    <div>
        <Switch>
        <Route exact path="/" component={ConnectPage} />
        <Route exact path="/MintPage" component={MintPage} />
        <Route exact path="/MyCryptonauts" component={MyCryptonauts} />
        <Route exact path="/AllCryptonauts" component={AllCryptonauts} />
        <Route path="*" component={NotFound} />
      </Switch>
    </div>
  );
}

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter><App /></BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

I have made a connect button in my first page, which redirects me to the mint section.

ConnectPage.js

import React from 'react'

export const ConnectPage = (props) => {

    async function Connect() {
        const web3 = window.web3
        const networkId = await web3.eth.net.getId()
        if (networkId === 4) {
            props.history.push("/MintPage")
        } else {
            await window.ethereum.request({
                method: 'wallet_switchEthereumChain',
                params: [{ chainId: '0x4' }],
            });
            props.history.push("/MintPage")
        }
    }

    return (
        <div>
            <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}><img src="https://nftornek.000webhostapp.com/frontend/cnlogo.png"></img></div>
            <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', marginTop: '5%'}}><button className="connectButton" onClick={Connect}>Enter the Universe</button></div>
        </div>
    )
}

This is where I make my mint transactions. I have put console.log("called checkChainID") to see how many times checkChainID function gets called. And it gets called 12 times every time page is loaded, and twice after I try to navigate to the same page.

As a beginner in all of these, I have gathered the information around I got from tutorials which are not much obviously, and tried to make a test application with routing(which I have made following by a tutorial too)

The routing worked as I exactly done everything in tutorial, and I wanted to keep going on this routing example for my app, but probably due to my lack of fundamental knowledge of React, I am doing something wrong inside the pages. I have been researching this issue for hours, but couldn't really understand what I can do to solve it.

I think it is because of useState because it renders the application every time when I call it, but I'm not sure if there is any way to prevent that from happening, or come up with a smarter way.

MintPage.js

import React, { useState } from 'react';
import Web3 from 'web3'
import Contract from "../src/build/Contract.json";

export const MintPage = (props) => {

  const web3 = window.web3;
  var [currentAccount, setCurrentAccount] = useState("0x0");
  var [currentBalance, setCurrentBalance] = useState("0");
  var [mintAmount, setMintAmount] = useState(1);
  const [addAmount, setAddAmount] = useState(true);
  const [subtractAmount, setSubtractAmount] = useState(false);

  window.ethereum.on('chainChanged', (_chainId) => checkChainID());
  window.ethereum.on('accountsChanged', (_accounts) => loadBlockchainData());
  checkChainID();

  async function checkChainID() {
    const networkId = await web3.eth.net.getId();
    if (networkId !== 4) {
      props.history.push("/")
    } else {
      loadBlockchainData();
    }
    console.log("called checkChainID")
  }

  async function loadBlockchainData() {

    window.web3 = new Web3(window.ethereum);
    const accounts = await web3.eth.getAccounts();
    setCurrentAccount(accounts[0]);
    getBalance(accounts[0]);
  }

  async function getBalance(acc) {
    const balance = await web3.eth.getBalance(acc);
    var balanceEth = web3.utils.fromWei(balance, 'ether');
    setCurrentBalance(parseFloat(balanceEth).toFixed(3)   " ETH");

    const SmartContractObj = new web3.eth.Contract(Contract.abi, "0x187FF2d65dd7204f11ea0487F2EED36378946902");
  }

  function MintPage() {
    props.history.push("/MintPage")
  }

  function MyCryptonauts() {
    props.history.push("/MyCryptonauts")
  }

  function AllCryptonauts() {
    props.history.push("/AllCryptonauts")
  }

  function Disconnect() {
    props.history.push("/")

  }

  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}><img src="https://nftornek.000webhostapp.com/frontend/cnlogo.png" width='500' height='180'></img></div>
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        <button className="regularButton divide" onClick={MintPage}>Mint</button>
        <button className="regularButton divide" onClick={MyCryptonauts}>My Cryptonauts</button>
        <button className="regularButton divide" onClick={AllCryptonauts}>All Cryptonauts</button>
        <button className="regularButton divide" onClick={Disconnect}>Disconnect</button>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center' }}><p className="accountText">Current Account: {currentAccount}</p></div>
      <div style={{ marginTop: '25%' }}></div>
      <div style={{ display: 'flex', justifyContent: 'center' }}><p className="accountText">Mint {mintAmount} Cryptonaut for XXX ETH</p></div>
      <div style={{ display: 'flex', justifyContent: 'center' }}><button className="amountButton divide" disabled={subtractAmount ? 0 : 1} onClick={() => {
        if (mintAmount <= 1) {
          setMintAmount(1);
          setSubtractAmount(false);
        } else {
          setMintAmount(mintAmount - 1);
        } if (mintAmount === 2) {
          setSubtractAmount(false);
        } if (mintAmount >= 1) {
          setAddAmount(true);
        }
      }}>-
      </button>
        <button className="mintButton divide" onClick={() => {
          console.log(mintAmount);
        }}>MINT
        </button>
        <button className="amountButton divide" disabled={addAmount ? 0 : 1} onClick={() => {
          if (mintAmount >= 5) {
            setMintAmount(5);
          } else {
            setMintAmount(mintAmount   1);
          }
          if (mintAmount === 4) {
            setAddAmount(false);
          } if (mintAmount >= 1) {
            setSubtractAmount(true);
          }
        }}> 
        </button>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', marginTop: '7px' }}><p className="accountText">Current Balance: {currentBalance}</p></div>
    </div>

  )
}

I would greatly appreciate any help. Please point me in the right direction! Might require a bit more of a detailed explanation as I am too new to this, though. :) Thanks all.

CodePudding user response:

The problematic piece is these three lines:

window.ethereum.on('chainChanged', (_chainId) => checkChainID());
window.ethereum.on('accountsChanged', (_accounts) => loadBlockchainData());
checkChainID();

You usually don't want to call a function directly in the render cycle of a component, since components can re-render for lots of different reasons. Every time this component renders it's adding an event listener to a global ethereum object, so you'll get one additional listener per component render. Since this is a functional component, you should wrap those lines in an effect hook so you can control when they run.

useEffect(() => {
  window.ethereum.on('chainChanged', (_chainId) => checkChainID());
  window.ethereum.on('accountsChanged', (_accounts) => loadBlockchainData());
  checkChainID();

  return () => {
    // remove the 'chainChanged' and 'accountsChanged' event listeners
  }
}, [])

The empty array tells this effect to only run once, when the component first renders. If you need it to run when some state changes, or you need access to any state variables inside the functions run in the effect, you need to list those in the dependency array.

You'll also want to return a function from the hook that removes your listeners so they don't hang around and continue to multiply every time a user visits the page, leaves, and returns.

You can read more about useEffect here.

  • Related