Crypto Dashboard Project
The following tutorial helps build a Cryptocurrency Portfolio Dashboard with React that fetches and displays the balance of different currencies for a given Ethereum address using the Bitquery Streaming API.
This is how it will look
The app has the following features:
- It renders an input field and a button to trigger the data fetching process.
- It displays a spinner while the data is being fetched.
- It displays a table with the currency name and the corresponding balance once the data is fetched.
- It handles errors during the data fetching process.
The app uses the following libraries and APIs:
- React: A JavaScript library for building user interfaces.
- Axios: A promise-based HTTP client for the browser and Node.js.
- Bitquery GraphQL API: A service that provides access to blockchain data through a GraphQL interface.
To use this code, you need to have the following dependencies installed in your project:
- react
- react-dom
- axios
- react-icons
Create App
Create an empty react app with the
npx create-react-app my-app
command and clear the defaults in app.js file.
Import Statements
import React, { useState } from 'react';
import axios from 'axios';
import './portfolio.css';
import { FaSpinner } from 'react-icons/fa';
Functional Component
function App() {
const [data, setData] = useState(null);
const [address, setAddress] = useState('');
const [isLoading, setIsLoading] = useState(false);
const fetchData = () => {
setIsLoading(true);
var query = `query MyQuery {
EVM(dataset: combined, network: eth) {
BalanceUpdates(
where: { BalanceUpdate: { Address: { is: "${address}" } } }
) {
Currency {
Name
}
balance: sum(of: BalanceUpdate_Amount)
}
}
}`;
var data = JSON.stringify({ query });
var config = {
method: 'post',
url: 'https://streaming.bitquery.io/graphql',
headers: {
'Content-Type': 'application/json',
Authorization: "Bearer your_access_token_here",
},
data: data
};
axios(config)
.then(function (response) {
setData(response.data.data.EVM.BalanceUpdates);
setIsLoading(false);
})
.catch(function (error) {
console.log(error);
setIsLoading(false);
});
};
State Management with useState
Hook
const [data, setData] = useState(null);
: Declares a state variabledata
that is initiallynull
. Also defines a setter functionsetData
to update the state value ofdata
.const [address, setAddress] = useState('');
: Declares a state variableaddress
that is initially an empty string. Also defines a setter functionsetAddress
to update the state value ofaddress
.const [isLoading, setIsLoading] = useState(false);
: Declares a state variableisLoading
that is initiallyfalse
. Also defines a setter functionsetIsLoading
to update the state value ofisLoading
.
fetchData
Method
const fetchData = () => { ... }
: Defines a methodfetchData
that makes an HTTP POST request to the Bitquery API to retrieve data for the entered Ethereum address.
Component Render
return (
<div>
<h1>Your Portfolio</h1>
<input className="address-input"
type="text"
value={address}
onChange={(event) => setAddress(event.target.value)}
placeholder="Enter address"
/>
<button onClick={fetchData} className="fetch-data-button"> {isLoading ? <FaSpinner className="spinner" /> : 'Fetch data'}</button>
{isLoading ? (
<div className="loading-spinner-container">
<FaSpinner className="loading-spinner" />
</div>
) : (
data && (
<table className="balance-table">
<thead>
<tr>
<th>Currency</th>
<th>Balance</th>
</tr>
</thead>
<tbody>
{data.map((item, index) => (
<tr key={index}>
<td>{item.Currency.Name}</td>
<td>{item.balance}</td>
</tr>
))}
</tbody>
</table>
)
)}
</div>
);
}
export default App;
CSS Styling
The app uses CSS classes defined in a separate portfolio.css file to style various elements:
.balance-list {
display: table-cell;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 2rem;
padding: 0;
list-style: none;
}
.balance-list li {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin: 1rem 0;
padding: 1rem 2rem;
font-size: 1rem;
background-color: #f8f8f8;
border: 1px solid #ccc;
border-radius: 5px;
}
.currency-name {
font-weight: bold;
font-size: 1.2rem;
margin-right: 1rem;
}
.balance-amount {
font-size: 1.2em;
font-family: monospace;
}
.fetch-data-button {
background-color: #4CAF50;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1.2em;
}
.fetch-data-button:hover {
background-color: #3e8e41;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fade-in {
animation: fadeIn 1s ease-in-out;
}
.address-input {
font-size: 1.2rem;
padding: 0.5rem;
border: none;
border-radius: 0.25rem;
box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.1);
outline: none;
margin-right: 0.5rem;
}