Building a React App to Fetch and Display BTC Unspent Outputs: A Beginner's Guide

mibii - Jul 29 - - Dev Community

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:

  1. Initial State: You have 1 BTC in your wallet, represented by a single UTXO.

  2. 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).
  1. 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.

Image description

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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: [],
}

Enter fullscreen mode Exit fullscreen mode

/* index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

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
    });
  };
}

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

import validate from 'bitcoin-address-validation';

// Inside handleSubmit function
if (!validate(address)) {
  alert('Please enter a valid BTC address');
  setIsLoading(false);
  return;
}

Enter fullscreen mode Exit fullscreen mode

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.

Image description


// 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>

Enter fullscreen mode Exit fullscreen mode

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">&lt;</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">&gt;</button>
</div>

Enter fullscreen mode Exit fullscreen mode

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!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player