Ethereum: Creating a Block Explorer with React (Part 3)

Displaying Block Info in React

This article continues the development of an Ethereum block explorer built in React. In part two we built a page that uses web3 to display a list of recent blocks. This third part of the series will continue the development and build a page to display details of an individual block.

Previously…

Check out these previous articles which describe setting up the dev environment and creating the first parts of the app:

Getting started

First we need to start geth. I’ve previously setup a private blockchain as described in my article here.

Note that the React development server will be running on port 3000, so make sure that geth is running on a different port. I’ll be using 3020.

Open a new terminal and change into the directory that contains privchain. Now start geth:

geth --port 3020 --networkid 58342 --nodiscover --datadir="privchain" --maxpeers=0 --autodag \
         --rpc --rpcport 8545 --rpcaddr 127.0.0.1 --rpccorsdomain "*" --rpcapi "eth,net,web3" \
         --ipcapi "eth,net,web3"

Next open another terminal window and change directory into the project folder blockexp. Then start the development server:

cd blockexp
npm start

Firefox should automatically launch with the the app loaded:

create-react-app default

Clicking on any of the block hash links should load a page saying Block Info:

create-react-app default

Building out the Block component

Open the file /src/components/Block/index.js. It should look like this:

import React, { Component } from 'react';
import './style.css';

class Block extends Component {
  render() {
    return (
      <div className="Block">
        <h2>Block Info</h2>
      </div>
    );
  }
}
export default Block;

Edit the file and add some additional import statements for web3 and Link. We’ll also instantiate web3 with the geth connection details.

import { Link } from 'react-router-dom'
import Web3 from 'web3';

var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));

The rest of this article will build out the Block component by introducing some additional functions:

  • constructor()
  • componentWillMount()
  • getBlockState()
  • render()
  • componentWillReceiveProps()

Read more about these functions and the React component lifecycle here: https://facebook.github.io/react/docs/react-component.html

constructor()

The documentation mentions that the constructor for a React component is called before it is mounted. Read more about it here: https://facebook.github.io/react/docs/react-component.html#constructor

Add the contructor function inside the existing Home class:

constructor(props) {
  super(props);
  this.state = {
    block: []
    }
  }

We use the contructor to inistialise the component State. You can read more about React states here: https://facebook.github.io/react/docs/state-and-lifecycle.html

componentWillMount()

Now we’ll add another new function componentWillMount(). According to the documentation, componentWillMount() is invoked immediately before mounting occurs. Read more about it here: https://facebook.github.io/react/docs/react-component.html#componentwillmount

We’ll use this function to read the block hash property, then invoke another function getBlockState() to read details of that block from the blockchain.

Add the componentWillMount function inside the existing Block class, below the constructor:

class Block extends Component {
  componentWillMount() {
    // Get the block hash from URL arguments (defined by Route pattern)
    var block_hash = this.props.match.params.blockHash;
    this.getBlockState(block_hash);
  }
  ...
}

getBlockState()

Next we’ll define the custom function getBlockState() which we called above. This function accepts a single parameter, a block hash, and uses web3 to retrieve the block details. Finally, it stores the block details in the component state. You can read more about React states here: https://facebook.github.io/react/docs/state-and-lifecycle.html

Add the following function below componentWillMount() {...}:

getBlockState(block_hash) {
  console.log("Block hash: " + block_hash);
  // Use web3 to get the Block object
  var currBlockObj = web3.eth.getBlock(block_hash);
  console.log(JSON.stringify(currBlockObj));
  // Set the Component state
  this.setState({
    block_id: currBlockObj.number,
    block_hash: currBlockObj.hash,
    block_ts: Date(parseInt(this.state.block.timestamp, 10)).toString(),
    block_txs: parseInt(currBlockObj.transactions.slice().length, 10),
    block: currBlockObj
  })
}

render()

The render() function returns HTML to display the block details on the page. Update it as below:

render() {
  const block = this.state.block;
  const difficulty = parseInt(block.difficulty, 10);
  const difficultyTotal = parseInt(block.totalDifficulty, 10);
  return (
    <div className="Block">
      <h2>Block Info</h2>
      <div>
        <table>
          <tbody>
            <tr><td className="tdLabel">Height: </td><td>{this.state.block.number}</td></tr>
            <tr><td className="tdLabel">Timestamp: </td><td>{this.state.block_ts}</td></tr>
            <tr><td className="tdLabel">Transactions: </td><td>{this.state.block_txs}</td></tr>
            <tr><td className="tdLabel">Hash: </td><td>{this.state.block.hash}</td></tr>
            <tr><td className="tdLabel">Parent hash: </td>
              <td><Link to={`../block/${this.state.block.parentHash}`}>{this.state.block.parentHash}</Link></td></tr>
            <tr><td className="tdLabel">Nonce: </td><td>{this.state.block.nonce}</td></tr>
            <tr><td className="tdLabel">Size: </td><td>{this.state.block.size} bytes</td></tr>
            <tr><td className="tdLabel">Difficulty: </td><td>{difficulty}</td></tr>
            <tr><td className="tdLabel">Difficulty: </td><td>{difficultyTotal}</td></tr>
            <tr><td className="tdLabel">Gas Limit: </td><td>{block.gasLimit}</td></tr>
            <tr><td className="tdLabel">Gas Used: </td><td>{block.gasUsed}</td></tr>
            <tr><td className="tdLabel">Sha3Uncles: </td><td>{block.sha3Uncles}</td></tr>
            <tr><td className="tdLabel">Extra data: </td><td>{this.state.block.extraData}</td></tr>
          </tbody>
        </table>
      </div>
    </div>
  );
}

Save the changes. If the server was already running, it should automatically refresh Firefox. Otherwise start it from a terminal with npm start. The block page should look like this:

create-react-app default

Updating the page with componentWillReceiveProps()

Finally, we want the page to refresh correctly when the user clicks a link to display a different block. You may notice that clicking on the parent hash link on the page doesn’t seem to work. To handle the refresh, we’ll add another function componentWillReceiveProps() which you can read more about here: https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops

Add the new function below the existing componentWillMount() function:

componentWillReceiveProps(nextProps) {
  var block_hash_old = this.props.match.params.blockHash;
  var block_hash_new = nextProps.match.params.blockHash;
  // compare old and new URL parameter (block hash)
  // if different, reload state using web3
  if (block_hash_old !== block_hash_new)
  this.getBlockState(block_hash_new);
}

This function compares the current block hash value to the one previously loaded. If the current value is different, the new block details are read from the blockchain and rendered using getBlockState().

Save the changes. If the npm server is already running, it should automatically refresh Firefox. Otherwise start it from a terminal with npm start. If you’d previously tried clicking the parent hash link, it should now load with the new block details:

create-react-app default

Return to the first page by clicking the Home link, then choose another block from the list and view it’s details. The block details should refresh each time, displaying the correct information.

Wrapping up

This series of articles has walked through creating a basic Ethereum block explorer using various tools. I hope you have enjoyed this series and are inspired to learn more.