-1

I am working on a simple DApp voting project . And I am using react for the front-end. As I am new to react I am facing problem with the use navigator. On my project I have two different components , candidate validator and voter validator which is assigned at the time of contract deployment and those are public state variables.

On the homepage , When I connect wallet , the components will be rendered based on the wallet address . Suppose, I am connecting as the owner , then after connection ,I'll be rendered to the owner component and same goes for other components. But I'm facing problem with the candidate validator and voter validator components. Sometimes when I connect wallet as a candidate validator , it redirects to voter validator page and vice versa.

Here is my contract:

// SPDX-License-Identifier: MIT
import "hardhat/console.sol";
pragma solidity ^0.8.10;

contract voting {
    address public owner;
    address public canValidator;
    address public voterValidator;

    //the deployer of the contract is the owner of the contract
    constructor(address validatorAdd, address _votervalidator) {
        owner = msg.sender;
        canValidator = validatorAdd;
        voterValidator = _votervalidator;
    }

    // Candidate information
    struct canInfo {
        address addr;
        string canName;
        uint256 totalVote;
        bool isVerifiedCan;
    }

    mapping(address => canInfo) public candidateInfo;

    //array to store candidate address
    address[] internal VcanAddress;

    function addrCandidate(address _addr, string memory _Canname) external {
        require(owner == msg.sender, "Only owner can add candidates");
        require(_addr != address(0), "Address is not valid");
        require(
            candidateInfo[_addr].addr == address(0),
            "Address is already added"
        );
        candidateInfo[_addr].addr = _addr;
        candidateInfo[_addr].canName = _Canname;
        VcanAddress.push(_addr);
    }

    //candidate verification//

    //this function only can be accessed by candidate validator
    //so should add a modifier or do the work inside function
    function verifyCandidate(address canADD) external {
        require(
            msg.sender == canValidator,
            "Only the candidate validator can access this function"
        );
        require(
            !candidateInfo[canADD].isVerifiedCan,
            "The candidate is not listed."
        );
        candidateInfo[canADD].isVerifiedCan = true;
    }

    //voter information
    struct voteInfo {
        address voteraddress;
        string voterName;
        string NID;
        bool isVerifiedvoter;
        bool hasVoted;
    }

    address[] internal allvoterAddr;
    mapping(address => voteInfo) public voterInfo;

    function addrVoter(
        address _voterAddress,
        string memory _name,
        string memory _NID
    ) external {
        require(owner == msg.sender, "Only owner can add candidates");
        require(_voterAddress != address(0), "Address is not valid");
        require(
            voterInfo[_voterAddress].voteraddress == address(0),
            "Address is already added"
        );
        voterInfo[_voterAddress].voteraddress = _voterAddress;
        voterInfo[_voterAddress].voterName = _name;
        voterInfo[_voterAddress].NID = _NID;
        allvoterAddr.push(_voterAddress);
    }

    //function to verify voter

    function verifyVoter(address voterADD) external {
        require(
            msg.sender == voterValidator,
            "Only the voter validator can access this function"
        );
        require(
            !voterInfo[voterADD].isVerifiedvoter,
            "The voter is not listed."
        );
        voterInfo[voterADD].isVerifiedvoter = true;
    }

    /* Only the verified voters can vote the verified candidates */

    function vote(address _candidate) external {
        require(voterInfo[msg.sender].isVerifiedvoter, "Voter is not verified");
        require(
            candidateInfo[_candidate].isVerifiedCan,
            "Candidate is not verified"
        );
        require(!voterInfo[msg.sender].hasVoted, "Voter already voted.");
        candidateInfo[_candidate].totalVote += 1;
        voterInfo[msg.sender].hasVoted = true;
    }

    function winnerCandidate() public view returns (address, uint256) {
        require(
            msg.sender == owner,
            "Function caller is not owner of the contract."
        );
        uint256 tempcount = 0;
        address tempaddress = address(0);
        for (uint256 i = 0; i < VcanAddress.length; i++) {
            if (candidateInfo[VcanAddress[i]].totalVote > tempcount) {
                tempcount = candidateInfo[VcanAddress[i]].totalVote;
                tempaddress = VcanAddress[i];
            }
        }

        return (tempaddress, tempcount);
    }

    function getAllVoterInfo() public view returns (voteInfo[] memory) {
        voteInfo[] memory allVoters = new voteInfo[](allvoterAddr.length);
        for (uint256 i = 0; i < allvoterAddr.length; i++) {
            allVoters[i] = voterInfo[allvoterAddr[i]];
        }
        return allVoters;
    }

    function getAllCandidateInfo() public view returns (canInfo[] memory) {
        canInfo[] memory allCandidates = new canInfo[](VcanAddress.length);
        for (uint256 i = 0; i < VcanAddress.length; i++) {
            allCandidates[i] = candidateInfo[VcanAddress[i]];
        }
        return allCandidates;
    }
}



This is home.jsx:

import React, { useState, useEffect } from "react";
import { ethers } from "ethers";
import { Routes, Route } from "react-router-dom";
import { useNavigate } from "react-router-dom";

const Home = ({ state, account }) => {
  const navigate = useNavigate();
  const { contract } = state;

  const [owner, setOwner] = useState("");
  const [canValidators, setcanValidators] = useState("");
  const [voterValidators, setvoterValidators] = useState("");
  const [voters, setVoters] = useState([]);
  const [candidates, setCandidates] = useState([]);

  useEffect(() => {
    async function getOwner() {
      const ownerAddress = await contract.owner();
      setOwner(ownerAddress);
    }

    async function getCanValidators() {
      const canValidator = await contract.canValidator();
      setcanValidators(canValidator);
    }
    async function getVoterValidators() {
      const voterValidator = await contract.voterValidator();
      setvoterValidators(voterValidator);
    }

    async function getVoters() {
      const voters = await contract.getAllVoterInfo();
      setVoters(voters);
    }
    async function getCandidates() {
      const candidates = await contract.getAllCandidateInfo();
      setCandidates(candidates);
    }

    if (contract) {
      getOwner();
      getCanValidators();
      getVoterValidators();
      getVoters();
      getCandidates();
    }
  }, [contract]);

  useEffect(() => {
    const allVoters = voters.flat();
    const allCandidates = candidates.flat();

    ///Debugging
    console.log(
      "Before navigation\n",
      "Candidate validator" + canValidators + "\n",
      "Voter validator" + voterValidators + "\n",
      "account" + account
    );

    if (account) {
      navigate(
        account === owner
          ? "/owner"
          : voterValidators
          ? "/VoterValidator"
          : canValidators
          ? "/CanValidator"
          : allVoters.includes(account)
          ? "/voter"
          : allCandidates.includes(account)
          ? "/candidate"
          : "/",
        { state: { isAccount: true } }
      );
      ///Debugging
      console.log(
        "After navigation" + "\n",
        "Candidate validator" + canValidators + "\n",
        "Voter validator" + voterValidators + "\n",
        "account" + account
      );
    }
  }, [
    account,
    navigate,
    owner,
    canValidators,
    voterValidators,
    voters,
    candidates,
  ]);

  return (
    <div className="container-fluid d-flex justify-content-center align-items-center py-5">
      <h1>This is the home page</h1>
    </div>
  );
};

export default Home;

This is app.jsx:

import { useState, useEffect } from "react";
import { ethers } from "ethers";
import { Routes, Route } from "react-router-dom";
import abi from "../../artifacts/contracts/voting.sol/voting.json";
import "./App.css";
import Home from "./components/home";
import Owner from "./components/owner";
import ValidatorCan from "./components/canvalidator";
import ValidatorVoter from "./components/votervalidator";
import Voter from "./components/voter";
import Candidate from "./components/candidate";
import { useNavigate } from "react-router-dom";

function App() {
  const navigate = useNavigate();
  const [state, setState] = useState({
    provider: null,
    signer: null,
    contract: null,
  });

  const [account, setAccount] = useState("None");
  const [isConnected, setIsConnected] = useState(false);
  const [hovered, setHovered] = useState(false);

  const connectWallet = async () => {
    const contractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
    const contractABI = abi.abi;

    try {
      const { ethereum } = window;

      if (ethereum) {
        await ethereum.request({ method: "eth_requestAccounts" });

        window.ethereum.on("chainChanged", () => {
          window.location.reload();
        });

        window.ethereum.on("accountsChanged", () => {
          window.location.reload();
        });

        const provider = new ethers.providers.Web3Provider(ethereum);
        const signer = provider.getSigner();
        const contract = new ethers.Contract(
          contractAddress,
          contractABI,
          signer
        );

        const accounts = await provider.listAccounts();
        setAccount(accounts[0]);
        setState({ provider, signer, contract });
        setIsConnected(true);
      } else {
        alert("Please install metamask");
      }
    } catch (error) {
      console.log(error);
    }
  };
  const [allowedRoutes, setAllowedRoutes] = useState([
    { path: "/", element: <Home state={state} account={account} /> },
  ]);

  useEffect(() => {
    async function checkAllowedRoutes() {
      if (account === "None") {
        setAllowedRoutes([
          { path: "/", element: <Home state={state} account={account} /> },
        ]);
        return;
      }

      const { contract } = state;
      const canValidator = await contract.canValidator();
      const voterValidator = await contract.voterValidator();
      const allVoters = await contract.getAllVoterInfo();
      const allCandidates = await contract.getAllCandidateInfo();

      const allowedRoutes = [
        { path: "/", element: <Home state={state} account={account} /> },
      ];

      if (account === (await contract.owner())) {
        allowedRoutes.push({
          path: "/owner",
          element: <Owner state={state} />,
        });
      } else if (canValidator === account) {
        allowedRoutes.push({
          path: "/CanValidator",
          element: <ValidatorCan state={state} />,
        });
      } else if (voterValidator === account) {
        allowedRoutes.push({
          path: "/VoterValidator",
          element: <ValidatorVoter state={state} />,
        });
      } else if (allVoters.flat().includes(account)) {
        allowedRoutes.push({
          path: "/voter",
          element: <Voter state={state} />,
        });
      } else if (allCandidates.flat().includes(account)) {
        allowedRoutes.push({
          path: "/candidate",
          element: <Candidate state={state} />,
        });
      }

      setAllowedRoutes(allowedRoutes);
    }

    checkAllowedRoutes();
  }, [account, state]);

  return (
    <div className="container">
      {isConnected ? (
        <button
          className="connect-wallet float-end"
          onMouseEnter={() => setHovered(true)}
          onMouseLeave={() => setHovered(false)}
        >
          {hovered ? `connected account:${account}` : "Connected"}
        </button>
      ) : (
        <button className="connect-wallet float-end" onClick={connectWallet}>
          Connect Wallet
        </button>
      )}
      <Routes>
        {allowedRoutes.map((route) => (
          <Route key={route.path} path={route.path} element={route.element} />
        ))}
      </Routes>
    </div>
  );
}

export default App;

When I connect as a candidate validator two things might happen,

  1. It loads the correct page when it loads the correct page
  2. It doesn't load the correct page doesn't load the correct page

When I connect as a voter validator two things might happen,

  1. It loads the correct page correct page
  2. It doesn't load the correct page wrong page

It seems that the voter validator address sometimes gets empty. How to resolve this problem ? [N.B : I am using hardhat local nodes and kite]

  • It's anti-pattern in React to store JSX in state. Store *just* the data and map the stored data to JSX when rendering. You are also likely having issues matching routes since they are being conditionally rendered. Better to create route protection components that check the access *when* the specific path is being accessed. – Drew Reese Apr 25 '23 at 18:17

1 Answers1

0

After much digging I found the I was only checking the value if true or false inside navigate. Here is the incorrect code:

if (account) {
      navigate(
        account === owner
          ? "/owner"
          : voterValidators //here
          ? "/VoterValidator"
          : canValidators //here
          ? "/CanValidator"
          : allVoters.includes(account)
          ? "/voter"
          : allCandidates.includes(account)
          ? "/candidate"
          : "/",
        { state: { isAccount: true } }
      );

Here is the code after I corrected it:

if (account) {
      navigate(
        account === owner
          ? "/owner"
          : account === voterValidators
          ? "/VoterValidator"
          : account === canValidators
          ? "/CanValidator"
          : allVoters.includes(account)
          ? "/voter"
          : allCandidates.includes(account)
          ? "/candidate"
          : "/",
        { state: { isAccount: true } }
      );