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. This journey covers various technologies, including React, Tailwind CSS, and working with APIs. Let’s break it down into key moments and learnings.

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.


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

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!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .