Building a frontend react app for Dapp from scratch: Part (3/4)
Tags: react, blockchain, dapp, tutorial
Published: 2021-11-28 09:56:20

![react & ethereum](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iixab283y7eglkwr0bog.png)

This part is the primary reason that motivated me to write this tutorial series in the first place. Tutorials on solidity were easy to find and they were good enough, but the problem was finding a good tutorial on how to build a frontend, how it interacts with the deployed smart contract and how to host it on cloud platforms like heroku.

> This section requires you to have some basic understanding of react such as components, useState, useEffect, etc. There are a ton of good resources on react, so please check them out if you are not familiar with these concepts.

> This project is hosted [here](https://simple-bank-co.herokuapp.com/).
Link to Github Repo of the project [here](https://github.com/wasimcodex/SimpleBank). This will serve as a reference for you, if at some point you are not able to follow the tutorial.

## Creating a react app

In the root directory, run 

```bash
npx create-react-app frontend
```
This will create a react app named frontend.

```bash
npm install @alch/alchemy-web3
npm install react-bootstrap bootstrap@5.1.3
npm install dotenv
npm install react-icons --save
```

This will install all the necessary packages for our project.

> Alchemy Web3 is a wrapper around `Web3.js` which provides some enhanced API methods.

## Setup

`create-react-app` initiates with some boilerplate code when we can get rid off.

Delete `App.test.js` `logo.svg` `reportWebVitals.js`, and `setupTests.js` from the src folder.

And make changes to your `index.js` and `App.js` file to look like this.

###index.js
```js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

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

###App.js
```js
import './App.css'

function App() {
  return (
    <>
    </>
  )
}

export default App
```

Now, go to `frontend/public/index.html` and add bootstrap CDN into the head tag.

```html
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
```

Create a `.env` file in the frontend directory and add your Alchemy API Key which we used in the previous section.

```
REACT_APP_ALCHEMY_KEY = <YOUR_API_KEY>
```

Now, we are all set to building our project.

## Getting artifacts

We need certain data about our deployed smart contract in the frontend in order to interact with it. In order to get that data we'll modify our deployment script at `SimpleBank/scripts/deploy.js`.

### deploy.js
```js
const { ethers, artifacts } = require('hardhat')

async function main() {
  const [deployer] = await ethers.getSigners()

  console.log('Deploying contracts with the account: ', deployer.address)

  const Bank = await ethers.getContractFactory('Bank')
  const bank = await Bank.deploy()

  console.log('Bank address: ', bank.address)

  saveArtifacts(bank)
}

// save the address and artifact of the deployed contract in the frontend
const saveArtifacts = (bank) => {
  const fs = require('fs')
  const artifactDir = __dirname + '/../frontend/src/artifacts'

  if (!fs.existsSync(artifactDir)) {
    fs.mkdirSync(artifactDir)
  }

  const bankArtifact = artifacts.readArtifactSync('Bank')

  const artifact = {
    address: bank.address,
    abi: bankArtifact.abi,
  }

  console.log('Saving artifacts to: ', artifactDir)

  fs.writeFileSync(artifactDir + '/Bank.json', JSON.stringify(artifact))
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })

```

We have added a new function `saveArtifacts()` and calling it at the end of `main` will save the deployed contract `address` and `abi` to `frontend/src/artifacts` when the contract is deployed.

We'll need to deploy our contract once again to get this data.

```bash
npx hardhat run .\scripts\deploy.js --network rinkeby
``` 

This should create a artifacts folder in `frontend/src` with a `Bank.json` file.

## Utility Functions

This is the part were we'll write the code to interact with our deployed smart contract.

Create a folder in `frontend/src` called `utils` and add the following files into it. This is where we'll write all our functions.

### walletFunctions.js
```js
export const connectWallet = async () => {
  if (window.ethereum) {
    try {
      const addresses = await window.ethereum.request({
        method: 'eth_requestAccounts',
      })
      const obj = {
        address: addresses[0],
        connected: true,
        status: '',
      }
      return obj
    } catch (error) {
      return {
        address: '',
        connected: false,
        status: error.message,
      }
    }
  } else {
    return {
      address: '',
      connected: false,
      status: (
        <a href="https://metamask.io/" target="_blank" rel="noreferrer">
          {' '}
          You need to install Metamask
        </a>
      ),
    }
  }
}

export const getWalletStatus = async () => {
  if (window.ethereum) {
    try {
      const addresses = await window.ethereum.request({
        method: 'eth_requestAccounts',
      })
      if (addresses.length > 0) {
        return {
          address: addresses[0],
          connected: true,
          status: '',
        }
      } else {
        return {
          address: '',
          connected: false,
          status: '🦊 Please connect to Metamask Wallet',
        }
      }
    } catch (error) {
      return {
        address: '',
        connected: false,
        status: error.message,
      }
    }
  } else {
    return {
      address: '',
      connected: false,
      status: (
        <a href="https://metamask.io/" target="_blank" rel="noreferrer">
          {' '}
          You need to install Metamask
        </a>
      ),
    }
  }
}
```



Here we have two functions, both are quite similar so I'll try to explain the key parts of both of them together.

```js
if (window.ethereum) {
    //does someting
} else {
    return {
      address: '',
      connected: false,
      status: (
        <a href="https://metamask.io/" target="_blank" rel="noreferrer">
          {' '}
          You need to install Metamask
        </a>
      ),
    }
  }
```
Here, `window.ethereum` checks if the browser has a wallet. If yes, it proceeds else we return an instruction to install metamask wallet.

```js
try {
      const addresses = await window.ethereum.request({
        method: 'eth_requestAccounts',
      })
      const obj = {
        address: addresses[0],
        connected: true,
        status: '',
      }
      return obj
    } catch (error) {
      return {
        address: '',
        connected: false,
        status: error.message,
      }
    }
```

Now, if there is a wallet available, we use `const addresses = await window.ethereum.request({ method: 'eth_requestAccounts', })` to request the wallet to connect an account to the app. If the connection was successful we get all the connected addresses and we return the first one, else we return the error message.

The `getWalletStatus` function also works pretty much the same. It checks the accounts that are connected to the wallet, if none it responds with request to connect.


### bankFunctions.js

```js
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require('@alch/alchemy-web3')
const web3 = createAlchemyWeb3(alchemyKey)

const { abi, address } = require('../artifacts/Bank.json')

export const depositEth = async (amount) => {
  if (parseFloat(amount) <= 0) {
    return {
      status: 'Please enter a valid amount',
    }
  }

  window.contract = await new web3.eth.Contract(abi, address)

  const WeiAmount = web3.utils.toHex(web3.utils.toWei(amount, 'ether'))

  const txParams = {
    to: address,
    from: window.ethereum.selectedAddress,
    value: WeiAmount,
    data: window.contract.methods.deposit().encodeABI(),
  }

  try {
    await window.ethereum.request({
      method: 'eth_sendTransaction',
      params: [txParams],
    })
    return {
      status: 'Transaction Successful. Refresh in a moment.',
    }
  } catch (error) {
    return {
      status: 'Transaction Failed' + error.message,
    }
  }
}

export const withdrawEth = async (amount) => {
  window.contract = await new web3.eth.Contract(abi, address)

  const WeiAmount = web3.utils.toWei(amount, 'ether')

  const txParams = {
    to: address,
    from: window.ethereum.selectedAddress,
    data: window.contract.methods.withdraw(WeiAmount).encodeABI(),
  }

  try {
    await window.ethereum.request({
      method: 'eth_sendTransaction',
      params: [txParams],
    })
    return {
      status: 'Transaction Successful. Refresh in a moment',
    }
  } catch (error) {
    return {
      status: 'Transaction Failed' + error.message,
    }
  }
}

export const getBalance = async () => {
  window.contract = await new web3.eth.Contract(abi, address)

  const reqParams = {
    to: address,
    from: window.ethereum.selectedAddress,
    data: window.contract.methods.getBalance().encodeABI(),
  }

  try {
    const response = await window.ethereum.request({
      method: 'eth_call',
      params: [reqParams],
    })
    const exhRate = await exchangeRate()
    const balance = web3.utils.fromWei(response, 'ether')
    return {
      inr: balance * exhRate,
      eth: balance,
      exhRate: exhRate,
    }
  } catch (error) {
    return {
      status: 'Check Failed ' + error.message,
    }
  }
}

export const exchangeRate = async () => {
  const response = await fetch(
    'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=inr',
  )
  const data = await response.json()
  return data.ethereum.inr
}

```
These are the functions that will handle app the bank features.

```js
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require('@alch/alchemy-web3')
const web3 = createAlchemyWeb3(alchemyKey)

const { abi, address } = require('../artifacts/Bank.json')
```
Firstly, we'll use out API Key to initialize our web3 instance and import `address` and `abi` from `artifacts/Bank.json`.

```js
if (parseFloat(amount) <= 0) {
    return {
      status: 'Please enter a valid amount',
    }
}
```
Then we have the `depositEth` function, which will be used to deposit amount. `amount` parameter will be in string format, so we convert it to float and ensure it is greater than 0.

```js
window.contract = await new web3.eth.Contract(abi, address)
```
Here we get an instance of our contract using `abi` and `address`.

```js
const WeiAmount = web3.utils.toHex(web3.utils.toWei(amount, 'ether'))
```
We convert the received parameter to Wei, which is the smallest denomination of ether ( 1 ether = 10^18 Wei). Then we convert it into Hex value which we'll use as a transaction parameter.

```js
const txParams = {
    to: address,
    from: window.ethereum.selectedAddress,
    value: WeiAmount,
    data: window.contract.methods.deposit().encodeABI(),
  }
```
Transaction parameters include `to: <contract_address>`, `from: <address_of_account_connected_to_app>`, `value: <amount_to_be_deposited>`, `data: <call_to_contract_function>`.

```js
try {
    await window.ethereum.request({
      method: 'eth_sendTransaction',
      params: [txParams],
    })
    return {
      status: 'Transaction Successful. Refresh in a moment.',
    }
  } catch (error) {
    return {
      status: 'Transaction Failed' + error.message,
    }
  }
}
```
Finally, we send the transaction. If successful we return a success message else we return the error message.

`withdrawEth` function as the name suggests is for transferring back the amount from bank to wallet. Its almost same are earlier, just instead of sending amount as a transaction parameter we'll send it as a parameter to the call function.

`getBalance` function return the balance available for the amount which calls it. The major difference here is that we use `method: 'eth_call'` since it is just a view function. We also use a `exchangeRate` function which is a simple `fetch` request to an api to get the current exchange rate of `ETH` to `INR` and return balance in both format.

## Components & CSS

Create a `components` folder in `frontend/src` and add these components to it.

These components are build pretty basic with some bootstrap elements and CSS is minimal, so we won't into too much detail for them.


### NavBar.jsx
```jsx
import { Container, Navbar } from 'react-bootstrap'

export default function NavBar() {
  return (
    <div>
      <Navbar bg="dark" variant="dark">
        <Container>
          <Navbar.Brand>SimpleBank & Co.</Navbar.Brand>
        </Container>
      </Navbar>
    </div>
  )
}
```

This is a simple navbar with a brand which we'll use in our app.


### StatusBox.jsx
```jsx
import { Alert, Container, Row, Col } from 'react-bootstrap'

const StatusBox = ({ status }) => {
  return (
    <Container
      className={status.length === 0 ? 'status-box-null' : 'status-box'}
    >
      <Row className="justify-content-center">
        <Col lg="6">
          <Alert variant="danger">{status}</Alert>
        </Col>
      </Row>
    </Container>
  )
}

export default StatusBox
```

This status bar will be used to inform the user whenever anything goes wrong.

### BankInfo.jsx

```jsx
import { IoIosRefresh } from 'react-icons/io'
import {
  Button,
  Container,
  FormControl,
  InputGroup,
  Col,
  Row,
  Alert,
} from 'react-bootstrap'
import { useState, useEffect } from 'react'

import { getBalance, depositEth, withdrawEth } from '../utils/bankFunctions'

const BankInfo = ({ onAccoutChange }) => {
  const [balanceINR, setBalanceINR] = useState(0)
  const [balanceETH, setBalanceETH] = useState(0)
  const [showDeposit, setShowDeposit] = useState(false)
  const [showWithdraw, setShowWithdraw] = useState(false)
  const [exhRate, setExhRate] = useState(0)
  const [inputINR, setInputINR] = useState(null)
  const [inputETH, setInputETH] = useState(null)
  const [response, setResponse] = useState(null)

  const handleShowDeposit = () => {
    setShowDeposit(true)
  }

  const handleShowWithdraw = () => {
    setShowWithdraw(true)
  }

  const handleClose = () => {
    setShowDeposit(false)
    setShowWithdraw(false)
    setInputINR(null)
    setInputETH(null)
    setResponse(null)
  }

  const checkBalance = async () => {
    const balance = await getBalance()
    setBalanceETH(balance.eth)
    setBalanceINR(balance.inr)
    setExhRate(balance.exhRate)
  }

  const handleInoutINR = (e) => {
    setInputINR(e.target.value)
    setInputETH((e.target.value / exhRate).toFixed(18))
  }

  const handleDeposit = async () => {
    setResponse(null)
    const deposit = await depositEth(inputETH.toString())
    setInputETH(null)
    setInputINR(null)
    setResponse(deposit.status)
  }

  const handleWithdraw = async () => {
    if (inputINR > balanceINR) {
      setResponse('Insufficient Balance')
    } else {
      setResponse(null)
      const withdraw = await withdrawEth(inputETH.toString())
      setInputETH(null)
      setInputINR(null)
      setResponse(withdraw.status)
    }
  }

  useEffect(() => {
    checkBalance()
  }, [onAccoutChange])

  return (
    <>
      <div className="balance-card">
        <h1>
          Your Balance
          <IoIosRefresh className="refresh-icon" onClick={checkBalance} />
        </h1>
        <h3 className="balance-inr">{parseFloat(balanceINR).toFixed(2)} INR</h3>
        <h3 className="balance-eth">{parseFloat(balanceETH).toFixed(4)} ETH</h3>
        {!showDeposit && !showWithdraw && (
          <div className="btn-grp">
            <Button
              className="deposit-btn"
              variant="success"
              onClick={handleShowDeposit}
            >
              Deposit
            </Button>
            <Button
              className="withdraw-btn"
              variant="warning"
              onClick={handleShowWithdraw}
            >
              Withdraw
            </Button>
          </div>
        )}
        {showDeposit || showWithdraw ? (
          <>
            <Container>
              <Row className="justify-content-center ">
                <Col md="6">
                  <InputGroup className="amount-input">
                    <FormControl
                      placeholder="Enter Amount in INR"
                      type="number"
                      value={inputINR > 0 ? inputINR : ''}
                      onChange={handleInoutINR}
                    />
                    <InputGroup.Text>INR</InputGroup.Text>
                  </InputGroup>
                </Col>
              </Row>
              <Row className="justify-content-center">
                <Col md="6">
                  <InputGroup className="amount-input">
                    <FormControl
                      placeholder="ETH Equivalent"
                      type="number"
                      value={inputETH > 0 ? inputETH : ''}
                      readOnly
                    />
                    <InputGroup.Text>ETH</InputGroup.Text>
                  </InputGroup>
                </Col>
              </Row>
            </Container>
            <div className="btn-grp">
              <Button
                className="deposit-btn"
                variant="success"
                onClick={showDeposit ? handleDeposit : handleWithdraw}
              >
                {showDeposit ? 'Deposit' : 'Withdraw'}
              </Button>
              <Button
                className="withdraw-btn"
                variant="info"
                onClick={handleClose}
              >
                Close
              </Button>
            </div>
            {response && (
              <Container>
                <Row className="justify-content-center">
                  <Col md="6">
                    <Alert variant="info">{response}</Alert>
                  </Col>
                </Row>
              </Container>
            )}
          </>
        ) : null}
      </div>
    </>
  )
}

export default BankInfo
```
This is the component that shows the account balance in INR and ETH and handles deposit and withdrawal. A bunch of state variables are used for smooth functioning of UI.

### ConnectBtn.jsx

```jsx
import { useState, useEffect } from 'react'
import { Button } from 'react-bootstrap'

import { connectWallet, getWalletStatus } from '../utils/walletFunctions'

export const ConnectBtn = ({ setStatus, setConnected, setWallet }) => {
  const [walletAddress, setWalletAddress] = useState('')

  const handleConnect = async () => {
    const walletResponse = await connectWallet()
    setStatus(walletResponse.status)
    setConnected(walletResponse.connected)
    setWalletAddress(walletResponse.address)
    setWallet(walletResponse.address)
  }

  useEffect(() => {
    const checkWalletStatus = async () => {
      const walletResponse = await getWalletStatus()
      setStatus(walletResponse.status)
      setConnected(walletResponse.connected)
      setWalletAddress(walletResponse.address)
      setWallet(walletResponse.address)
    }

    const walletListener = () => {
      if (window.ethereum) {
        window.ethereum.on('accountsChanged', (accounts) => {
          checkWalletStatus()
        })
      }
    }

    checkWalletStatus()
    walletListener()
  }, [setConnected, setStatus, setWallet])

  return (
    <div className="connect-btn">
      <Button variant="primary" onClick={handleConnect}>
        {walletAddress.length === 0
          ? 'Connet Wallet'
          : 'Connected: ' +
            String(walletAddress).substring(0, 6) +
            '...' +
            String(walletAddress).substring(38)}
      </Button>
    </div>
  )
}

export default ConnectBtn
```
`ConnectBtn` component displays the status of wallet connection to the app and also used to request connection when not connected.

### Footer.jsx

```jsx
import { IoIosInformationCircleOutline } from 'react-icons/io'

const Footer = () => {
  return (
    <footer className="footer">
      <p className="footer-text">
        <IoIosInformationCircleOutline className="info-icon" /> This application
        in running on rinkeby test network. Please only use test Ethers.
      </p>
    </footer>
  )
}

export default Footer
```
A simple footer component to warn the user to only use test ethers.

### index.css
```css
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

.amount-input {
  margin: 10px;
}

.balance-card {
  margin-top: 20px;
  text-align: center;
}

.balance-eth {
  color: rgb(19, 202, 28);
}

.balance-inr {
  color: rgb(25, 214, 214);
}

.btn-grp {
  margin: 20px;
}

.deposit-btn {
  margin-right: 20px;
}

.footer {
  position: fixed;
  left: 0;
  bottom: 0;
  width: 100%;
  padding: 5px;
  background-color: teal;
  color: white;
  text-align: center;
}

.footer-text {
  font-size: large;
}

.info-icon {
  font-size: x-large;
}

.withdraw-btn {
  margin-left: 20px;
}

.connect-btn {
  text-align: center;
  margin: 20px;
}

.refresh-icon {
  margin-left: 10px;
  font-size: 28px;
  cursor: pointer;
}

.status-box {
  margin-top: 20px;
  text-align: center;
}

.status-box-null {
  display: none;
}

```
Styling is nothing fancy, just to align all the components properly.

## Adding all components to App.js

### App.js

```js
import { useState } from 'react'

import NavBar from './components/NavBar'
import ConnectBtn from './components/ConnectBtn'
import StatusBox from './components/StatusBox'
import BankInfo from './components/BankInfo'
import Footer from './components/Footer'

function App() {
  const [status, setStatus] = useState('')
  const [connected, setConnected] = useState()
  const [wallet, setWallet] = useState()
  return (
    <>
      <NavBar />
      <ConnectBtn
        setStatus={setStatus}
        setConnected={setConnected}
        setWallet={setWallet}
      />
      <StatusBox status={status} />
      {connected && <BankInfo onAccoutChange={wallet} />}
      <Footer />
    </>
  )
}

export default App
```
Add all the components to `App.js` in order to render them in our app.

This wraps up our react app. Run the following command to run it on localhost.

```
npm start
```

If things doesn't work as expected, checkout github repo [here](https://github.com/wasimcodex/SimpleBank) for reference.

In the next tutorial, we'll see how to host this app on heroku. Click [here](https://dev.to/wasimcodex/deploying-dapp-to-heroku-part-44-1305).