Categories
blockchain moralis React web3

Build a Token Dashboard with React Hooks, Moralis API, and Bootstrap CSS

Introduction

Ever since I discovered Moralis, I’ve always wanted to play around with the API to see its capabilities. If you’re interested in my work with QuickNode API, you can find it here

Below is a screenshot of our app’s final version.

Homepage:

The Dashboard:

INITIAL SETUP

  • I used the Moralis react starter template you can find here.
npx create-react-app moralis-react-dashboard

cd moralis-react-dashboard

yarn add moralis

yarn start
  • If you want to build the app from the ground up, there’s also a Moralis react wrapper. You can follow the installation process here.
  • I also used an HTML template which you can find here. You will find most of the CSS here
  • Install Bootstrap CSS with:
npm i bootstrap 
  • Also, add this to your index.js file.
import "bootstrap/dist/css/bootstrap.min.css";
  • Get your env. variables and fill it here:
REACT_APP_MORALIS_APPLICATION_ID=YOUR_APP_ID
REACT_APP_MORALIS_SERVER_URL=

To do this,
i. Sign up at https://moralis.io/
ii. On the admin page, click the “Create a new app” button -> “Add new Mainnet Server” and fill in the details as shown below and add instance. It may take some time until a new server is provisioned.

iv. Click the “View Details” button, copy the Server URL and Application ID to the env file.

INITIALIZE MORALIS

The next step is initializing Moralis. To do this, perform the following steps:

import moralis from "moralis";
  • Add the following code:
moralis.initialize(process.env.REACT_APP_MORALIS_APPLICATION_ID);
moralis.serverURL = process.env.REACT_APP_MORALIS_SERVER_URL;
const initialUser = moralis.User.current();
  • We will also be using React useState to initialize the state with our user:
const [user, setUser] = useState(initialUser);
  • We then create a login function that will help users login. Moralis uses Metamask for logging in users. Be sure to have this installed on your browser.
const onLogin = async () => {
const user = await moralis.authenticate();
setUser(user);
};
<button class="btn-outline-lg" onClick={onLogin}> Login</button>
  • Lastly, we will create a Dashboard.js file in the components folder. We will then tell the app to run the Dashboard file when the user is logged in. If not, we want it to show the landing page code.

The entire App.js file is shown below:

import { useState } from "react";
import moralis from "moralis";
import "./App.css";
import Dashboard from "./components/Dashboard";
import img1 from "./images/decoration-star.png";
import img2 from "./images/dash.jpeg";

moralis.initialize(process.env.REACT_APP_MORALIS_APPLICATION_ID);
moralis.serverURL = process.env.REACT_APP_MORALIS_SERVER_URL;
const initialUser = moralis.User.current();

function App() {
  const [user, setUser] = useState(initialUser);

  const onLogin = async () => {
    const user = await moralis.authenticate();
    setUser(user);
  };

  return (
    <div className="App">
      {user ? (
        <Dashboard />
      ) : (
        <div>
          {/* Nav */}
          <nav
            id="navbarExample"
            class="navbar navbar-expand-lg fixed-top navbar-light"
            aria-label="Main navigation"
          >
            <div class="container">
              <a class="navbar-brand logo-text py-3" href="/">
                Moralised
              </a>
            </div>
          </nav>
          {/* hero */}
          <header id="header" class="header">
            <img class="decoration-star" src={img1} alt="alternative" />
            <img class="decoration-star-2" src={img1} alt="alternative" />
            <div class="container">
              <div class="row">
                <div class="col-lg-7 col-xl-5">
                  <div class="text-container">
                    <h1 class="h1-large">Moralised</h1>
                    <p class="p-large">
                      Your minimalist ETH dashboard. See your last transactions
                      and tokens balance.
                    </p>
                    <button class="btn-outline-lg" onClick={onLogin}>
                      Login
                    </button>
                  </div>
                </div>
                <div class="col-lg-5 col-xl-7 rounded">
                  <div class="image-container ">
                    <img
                      class="img-fluid rounded"
                      src={img2}
                      alt="alternative"
                    />
                  </div>
                </div>
              </div>
            </div>
          </header>
          {/* hero */}
        </div>
      )}
    </div>
  );
}

export default App;

DASHBOARD

For our dashboard, we need to import moralis.

import moralis from "moralis";

Log Out

To log out the user from the dashboard, create an onLogout function with the following code:

const onLogout = () => {
    moralis.User.logOut();
 };

Setting State

To set the state of our dashboard page, we first need to import  React Hooks: useState and useEffect.

We then call useState to initialize our tokens and transactions state.

const [tokens, setTokens] = useState([]);
const [transactions, setTransactions] = useState([]);

Next, we create the “getBalance” and “getTransactions” functions calling the Moralis Token Balance and Transactions queries. You can check out the links to see the official documentation about each query. We will be displaying only 15 of the most recent transactions, so we sliced our initial query to a new array.

  const getBalance = async () => {
    const balances = await moralis.Web3.getAllERC20();
    setTokens((tokens) => [...tokens, ...balances]);
  };
  async function getTransactions() {
    // get mainnet transactions for the current user
    const userTrans = await moralis.Web3.getTransactions();
    const lastTen = userTrans.slice(0, 15);
    setTransactions((transactions) => [...transactions, ...lastTen]);
  }

N.B You can console.log the results of the queries to see how they are received.

To ensure our queries run at least once, we call them in a useEffect hook and pass an empty array as a dependency.

useEffect(() => {
   getBalance();
 getTransactions();
}, []);

Displaying Our Token Balance and Transactions Queries

We created two buttons for displaying both queries

<button type="button" class="btn btn-primary me-2" onClick={showTabs}>
  Token Balances
</button>
<button type="button" class="btn btn-secondary me-2" onClick={showTabs}>
  Recent Transactions
</button>

A useState hook makes these buttons act as tabs:

const showTabs = () => {
   setShowTransactions((showTransactions) => !showTransactions);
   setShowTokens((showTokens) => !showTokens);
};

We then mapped the state and styled them using Bootstrap list group classes. 

Here’s the complete code for the Dashboard.js file.

import { useState, useEffect } from "react";
import moralis from "moralis";

export default function Dashboard() {
  const [tokens, setTokens] = useState([]);
  const [transactions, setTransactions] = useState([]);
  const [showTokens, setShowTokens] = useState(true);
  const [showTransactions, setShowTransactions] = useState(false);

  useEffect(() => {
    getBalance();
    getTransactions();
  }, []);

  const onLogout = () => {
    moralis.User.logOut();
  };

  const getBalance = async () => {
    const balances = await moralis.Web3.getAllERC20();
    setTokens((tokens) => [...tokens, ...balances]);
  };
  async function getTransactions() {
    // get mainnet transactions for the current user
    const userTrans = await moralis.Web3.getTransactions();
    const lastTen = userTrans.slice(0, 15);
    setTransactions((transactions) => [...transactions, ...lastTen]);
  }

  const showTabs = () => {
    setShowTransactions((showTransactions) => !showTransactions);
    setShowTokens((showTokens) => !showTokens);
  };

  return (
    <div className="mx-auto">
      <nav
        id="navbarExample"
        class="navbar navbar-expand-lg fixed-top navbar-light"
        aria-label="Main navigation"
      >
        <div class="container">
          <a class="navbar-brand logo-text" href="/">
            Moralised
          </a>
          <span class="nav-item">
            <a href="/" className="btn-outline-lg" onClick={onLogout}>
              Logout
            </a>
          </span>
        </div>
      </nav>

      <div className="mt-5 pt-5 mx-auto text-center">
        <h1 className="text-success">Welcome To Moralised</h1>
        <p>
          Click the buttons below to get the latest token balances, gas stats
          and the most recent of your transactions
        </p>
      </div>

      <div className="text-start mx-5 px-5 my-3">
        <button type="button" class="btn btn-primary me-2" onClick={showTabs}>
          Token Balances
        </button>
        <button type="button" class="btn btn-secondary me-2" onClick={showTabs}>
          Recent Transactions
        </button>
      </div>

      <div className="mx-5 px-5">
        {showTokens ? (
          <ul className="list-group text-start">
            {tokens.map((bal) => (
              <li key={bal.id} className="list-group-item">
                {bal.name} :{" "}
                <span>
                  {(bal.balance / Math.pow(10, bal.decimals)).toFixed(6)}{" "}
                </span>
              </li>
            ))}
          </ul>
        ) : null}
      </div>
      <div className="mx-5 px-5">
        {showTransactions ? (
          <ul className="list-group text-start">
            {transactions.map((item) => (
              <li className="list-group-item" key={item.id}>
                <span className="text-success pe-2">Block Number:</span>
                {item.block_number}{" "}
                <span className="text-primary px-5">
                  {" "}
                  Time: {item.block_timestamp.toDateString()}{" "}
                </span>{" "}
                <span className="text-danger px-2">Outgoing address:</span>
                {item.to_address}{" "}
                <span className="px-5">
                  <a
                    href={"https://etherscan.io/tx/" + item.hash}
                    className="link-primary"
                  >
                    Etherscan Link
                  </a>
                </span>
              </li>
            ))}
          </ul>
        ) : null}
      </div>
    </div>
  );
}

There you have it. To access the complete code as well the images used, here’s the Github repository. Remember to add your .env file to the .gitignore file if you decide to push it to a repository.

I really enjoyed building this minimalist app. Do remember to drop by as I explore other web3 offerings.