Diving into different technologies can be both exciting and daunting. One effective way to master new skills is by working on real-world projects. Today, I’m sharing my experience developing a React application to fetch and display Bitcoin (BTC) unspent outputs.
A brief terminology induction:
- Transactions: In Bitcoin, transactions involve moving funds from one address to another. Each transaction has one or more inputs (the source of the funds) and one or more outputs (the destination of the funds). All transactions involve moving funds from UTXOs to new outputs. UTXOs are "Spent" When Used as Inputs: When a UTXO is used as an input in a transaction, it is considered "spent" and can no longer be used as an input in another transaction.
Example: Spending 0.3 BTC from an Initial 1 BTC:
Initial State: You have 1 BTC in your wallet, represented by a single UTXO.
Transaction: You want to send 0.3 BTC to a friend. The transaction will have:
- Input: The single UTXO containing 1 BTC.
- Outputs: Two outputs:
- 0.3 BTC to your friend's address.
- 0.7 BTC back to your own address (change).
- Result: The original UTXO containing 1 BTC is now "spent" (it's used as an input). You now have two UTXOs:
- 0.7 BTC in your wallet.
- 0.3 BTC in your friend's wallet.
Setting Up the Project
Initializing the React App
First, set up your React project using Create React App. This command-line tool simplifies the process of setting up a new React project with a pre-configured development environment.
npx create-react-app btc-unspent-outputs
cd btc-unspent-outputs
Installing Tailwind CSS
Tailwind CSS is a utility-first CSS framework that helps you quickly style your application. It’s highly customizable and perfect for building modern web applications.
npm install -D tailwindcss
npx tailwindcss init
Configure Tailwind in tailwind.config.js
and include it in your CSS files.
// tailwind.config.js
module.exports = {
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
/* index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Developing the Application
Managing State and Form Submission
We use React’s useState
to manage the state for unspent outputs, the BTC address input, loading state, and pagination. Handling form submission involves fetching unspent outputs for the provided BTC address.
import React, { useState } from 'react';
function App() {
const [outputs, setOutputs] = useState([]);
const [address, setAddress] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage] = useState(10);
const handleSubmit = (e) => {
e.preventDefault();
setIsLoading(true);
fetchUnspentOutputs(address)
.then(data => {
setOutputs(data);
setIsLoading(false);
setCurrentPage(1);
})
.catch(err => {
console.error('Error fetching unspent outputs:', err);
setOutputs([]);
setIsLoading(false);
});
};
const fetchUnspentOutputs = async (btcAddress) => {
const response = await fetch(`https://blockchain.info/unspent?active=${btcAddress}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (!data.unspent_outputs || !Array.isArray(data.unspent_outputs)) {
throw new Error('Invalid response format');
}
return data.unspent_outputs.map((output) => ({
txHash: output.tx_hash_big_endian,
outputIndex: output.tx_output_n,
value: output.value / 100000000,
confirmations: output.confirmations
}));
};
const formatBTC = (btc) => {
return btc.toLocaleString('en-US', {
minimumFractionDigits: 8,
maximumFractionDigits: 8
});
};
}
Validating BTC Address
To ensure the user inputs a valid BTC address, we use the bitcoin-address-validation
library. This helps prevent unnecessary API calls with invalid addresses.
npm install bitcoin-address-validation
import validate from 'bitcoin-address-validation';
// Inside handleSubmit function
if (!validate(address)) {
alert('Please enter a valid BTC address');
setIsLoading(false);
return;
}
Displaying Data in a Table
Once the data is fetched, we display it in a table. We ensure that each transaction hash is clickable, leading to a detailed view on a blockchain explorer.
// Inside the return statement
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Transaction Link</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Transaction Hash</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Output Index</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Value (BTC)</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Confirmations</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{outputs.map((output, index) => (
<tr key={index}>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 break-all">
<a href={`https://www.blockchain.com/explorer/transactions/btc/${output.txHash}`} target="_blank" rel="noopener noreferrer">
url_link_check_tx_hash
</a>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{output.txHash}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{output.outputIndex}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{formatBTC(output.value)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{output.confirmations}
</td>
</tr>
))}
</tbody>
</table>
</div>
Pagination
To handle pagination, we slice the data array and display a subset of items based on the current page and items per page. We also add buttons to navigate between pages.
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentOutputs = outputs.slice(indexOfFirstItem, indexOfLastItem);
const paginate = (pageNumber) => setCurrentPage(pageNumber);
// Pagination buttons inside return statement
<div className="flex justify-center items-center mt-4 space-x-2">
<button onClick={() => paginate(currentPage - 1)} disabled={currentPage === 1} className="px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:bg-gray-200 disabled:text-gray-400"><</button>
<span className="text-sm text-gray-700">Page {currentPage}</span>
<button onClick={() => paginate(currentPage + 1)} disabled={indexOfLastItem >= outputs.length} className="px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:bg-gray-200 disabled:text-gray-400">></button>
</div>
Deployed - https://btcunspentoutputchecker.netlify.app/
Link to download source code https://buymeacoffee.com/techmobilebox/
Conclusion
Building this React application to fetch and display BTC unspent outputs has been an enlightening experience. It not only reinforces your React and Tailwind CSS skills but also teaches you how to work with external APIs and handle data efficiently. This project is a great example of how combining different technologies can result in a powerful and functional web application.
For beginners, the key takeaways are:
-
State Management: Using React’s
useState
to manage different states in the application. - Form Handling: Implementing form submission and data fetching with proper error handling.
- Data Display: Using tables to display fetched data and implementing pagination for better user experience.
- API Integration: Learning how to interact with external APIs and handle JSON responses.
By working through this project, you’ll gain a deeper understanding of these concepts and be better prepared for more advanced web development challenges. Happy coding!