javascriptreactjsreact-router-domweb3-react

Specific React model doesn't load sometimes and needs rerendering


I am a beginner both in Solidity and React, right now I am trying to create a front end application with React to interact with my smart contract. I have made a connect button in the first page of my application which takes the user to the next page after logging in with MetaMask.

In the second page, I load most of the information about the contract like prices, last minted NFT etc. Everything looks like they work in order except for setting my last minted NFT model.

Here's how my index.js looks, I don't use app.js, I use index.js instead of it. I have followed some tutorials about navigating through pages and followed it, the issue is not on this page, I'm just showing it for further information:

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")
);

Now, my minting page has the problem that I am talking about. I have a file named jsonModel.js in my model folder which looks like this:

export default {
    dna: '?',
    name: '?',
    edition: 0,
    attributes: []
}

I set the values like '?' so I can see if they are not loaded. Below, there is my MintPage.js codes which I do basically everything on this page. Here's the thing: most of the time, id and name comes as "?" which shows they are not loaded, and I load some more data in the attributes array, in the function called "setLatestMint" but it seems it doesn't work most of the time. When I reload the page, it works. Sometimes it doesn't.

enter image description here enter image description here enter image description here

I have noticed that when I click on a button which use useState parameters, the models load, just like when I reloaded the page. So it seems that the problem is with rendering. When I do something to trigger rerendering, the data seems to load. I have tried to manually rerender the component, I know it is not a recommended way but even that doesn't work because I cannot use "this". so I am stuck here with no knowledge whatsoever, doing research for hours but couldn't solve the problem. Any help will be appreciated greatly!

import React, { useState, useEffect } from 'react';
import Web3 from 'web3'
import impContract from "../src/impContract.js";
import jsonModel from "./model/jsonModel";

export const MintPage = (props) => {

  const web3 = window.web3;
  const [currentAccount, setCurrentAccount] = useState("0x0");
  const [currentBalance, setCurrentBalance] = useState("0");
  const [mintAmount, setMintAmount] = useState(1);
  const [mintCost, setMintCost] = useState(0);
  const [latestMintPic, setLatestMintPic] = useState("");
  const [feedback, setFeedback] = useState("Maybe it's your lucky day!");
  const [addAmount, setAddAmount] = useState(true);
  const [subtractAmount, setSubtractAmount] = useState(false);
  const [claimingNft, setClaimingNft] = useState(false);
  let [model] = useState(jsonModel);
  let lastMintJson;

  useEffect(() => {

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

    return () => { }
  }, [])

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

  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");
    loadContract();
  }

  async function loadContract() {
    const ContractObj = impContract;
    const costResult = await ContractObj.methods.cost().call();
    var costEth = web3.utils.fromWei(costResult, 'ether');
    setMintCost(parseFloat(costEth).toFixed(2));
  }

  async function setLatestMint() {
    const ContractObj = impContract;
    const latestMintResult = await ContractObj.methods.totalSupply().call();
    setLatestMintPic("https://nftornek.000webhostapp.com/cryptonauts/image/" + latestMintResult + ".png");
    lastMintJson = "https://cors-anywhere.herokuapp.com/https://nftornek.000webhostapp.com/cryptonauts/json/" + latestMintResult + ".json";


    var x = new XMLHttpRequest();
    x.open('GET', lastMintJson);
    x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    x.onload = function () {
      model = setNFTModel(JSON.parse(x.responseText));
      console.log(model);

    };
    x.send();

  }

  function setNFTModel(jsonObj) {
    model.dna = jsonObj.dna;
    model.name = jsonObj.name;
    model.edition = jsonObj.edition;

    if (model.attributes.length > 0) {
      model.attributes = []
    }
    for (var x = 0; x < jsonObj.attributes.length; x++) {
      model.attributes.push(jsonObj.attributes[x]);
    }

    return model;

  }


  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={{ display: 'flex', justifyContent: 'center', }}><h1>Latest Mint:</h1></div>
      <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '30px', height: '350px' }}>
        <div style={{ width: '350px', border: '2px solid #38495a', borderRadius: '5px' }}><img src={latestMintPic}></img>
        </div>
        <div style={{ width: '300px', padding: '10px', border: '2px solid #38495a', borderRadius: '4px', backgroundColor: 'rgba(56, 73, 90, 0.25)'}}><t1>ID: {model.edition}<br></br> Name: {model.name}
          <table className="tableClass t1">
            <tbody>
              {model.attributes.map(item => (
                <tr key={item.trait_type}>
                  <td key={1}>{item.trait_type}:</td>
                  <td key={2}>{item.value}</td>
                </tr>
              ))}
            </tbody>
          </table></t1></div>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', color: '#38495a' }}>{feedback}</div>
      <div style={{ display: 'flex', justifyContent: 'center' }}><p className="accountText">Mint <b>{mintAmount}</b> Cryptonaut for <b>{mintCost * mintAmount}</b> 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" disabled={claimingNft ? 1 : 0} onClick={() => {
          claimNFTs(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: <b>{currentBalance}</b></p></div>
    </div>

  )
}

Solution

  • I have solved the issue by changing these lines:

    var x = new XMLHttpRequest();
        x.open('GET', lastMintJson);
        x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        x.onload = function () {
          setModel(setNFTModel(JSON.parse(x.responseText)));
          console.log(model);
    
        };
        x.send();
    

    into these:

    let res = await axios.get(lastMintJson);
        setModel(setNFTModel(res.data))
    

    Now, every time the data loads. I think it was because that I couldn't make the older request asynchronously. But now, with axios, I can use the await keyword and prevent it from rendering before loading. I may be wrong about the idea, but it works right now.