Creating an Ethereum Block Explorer
Hello guys. As you might know that I have been studying Ethereum Developer Bootcamp created by Alchemy University and my recent blog posts were about weekly projects. This post too will be about one of the projects, which is creating a block explorer.
This project took me a lot of time to complete because this particular one required quite a lot of knowledge/experience in React and I almost know nothing about it. This might be a simple project for people with a tech background but it was hard for me, it was challenging and sometimes disappointing. The most important thing I did while doing this project was not quitting no matter how much I wanted to and trying to complete it. The reason I am writing this post right now is that I know that there must be other people like me who are taking this Bootcamp with a little knowledge about React, struggling and planning to quit. I want to show that is normal and I want to encourage them.
What is this project? How will it work?
This is a React project that gets data from the Ethereum mainnet with an API and shows those data to the users. In the beginning, we get a base code with only one page from the Alchemy University team and then we try to improve it into a working website. You can get the base code by cloning this repository.
As I said, the base code only has one page which is the home/index page. What do we need to do as a developer? So, let's think about how should our project work.
It has to show blocks and transactions on the home page. All of this information should come from the blockchain.
We should be able to click block numbers and the website should direct us to that specific block's page.
Like blocks, the website should direct us to individual addresses and transaction pages when clicked.
We can improve this or add new features etc but it is basically like this. So, how will we create it?
Creating a structure and learning how to navigate
Like I said before, I almost know nothing about React and I have to create a basic website. What will this website have? It will have a home page, a block page, a transaction page and an address page, right? This was the simplest structure of the project.
Okay, I created those pages as different JS files but, what? I don't know how to switch pages. So, first things first. I should learn how to change pages. Every page must have a different URL, every URL should show me(render) different content, and I should be able to navigate between them. It turns out the thing I needed to use is React Router. You can read the link I gave for detailed information, or you can watch the first few sections of this video to understand how routes and react router work. We simply need to create different routes for every possible page and we will decide what is going to be rendered in that particular route. But first, we need to install React router with npm i react-router-dom
. After I learned how the router works my code was like this:
<Routes>
<Route path="/" element={<Home />} />
<Route path="/block/:id" element={<Block />}/>
<Route path="/transaction/:id" element={<Transaction />}/>
<Route path="/address/:id" element={<Address />}/>
<Route path="/blockTransactions/:id" element={<BlockTransactions />}/>
</Routes>
You can see that every <Route/>
has a path and element. The path here is the URLs of the pages and the elements are the functions (We imported those functions from our JS files which are separate for every page) that will render our pages.
In the code above, we have 5 different paths. In 4 of them, you can see :id
which means "anything can be in here". For example, if the URL is ".../block/12345" the application will render a block page for block number 12345 using that path's element.
Okay, we created JS files for each page, installed React router, created our route paths and assigned elements (which are not doing anything currently). Now it's time to fill those JS files and make those elements actually work.
Creating our home page
If I was trying to create a commercial block explorer app I would probably have to add a lot of things to the home page but it was just a beginner project for me to learn new things and I only wanted to add two tables: The latest blocks and the latest transactions. What should my app do in the backend?
It has to get the block number of the latest block
It has to have an array for the last 10 - 15 blocks. After getting the last block's number, it should loop 15 times, get those blocks and push them to the array of blocks.
It should also have an array for the latest transactions. Same way as above it should loop the transactions that were in the last block, and push them to the array of transactions.
What about frontend?
It should render a table using these block and transaction arrays.
It should create a row for every element in those arrays.
Some of the data (like block number) in these rows should be links/anchors and should navigate the user to different URLs (like .../blocks/blocknumber).
Okay, now we know what it should do, and we have a plan. Let's execute that plan!
useEffect(() => {
let blockArray = [];
let transactionArray = [];
const getLatestBlocks = async () => {
const blockNumber = await alchemy.core.getBlockNumber()
setBlockNumber(blockNumber);
for (let i = 0; i < 15; i++){
const block = await alchemy.core.getBlock(blockNumber - i);
blockArray.push(block);
}
setLatestBlocks(blockArray)
console.log("latest block: ", latestBlocks);
}
getLatestBlocks();
}, []);
Above, you can see some parts of the code for my home page. That's half of it but you get the idea. The function gets the number of the latest block and then gets the previous blocks 15 times and sets the value. It happens the same way for transactions too but I didn't want to write them all the way. By the way, on top of this function, there are some useState
values like const [latestBlocks, setLatestBlocks] = useState();
etc.
As you can see this function is inside of the useEffect hook and when I tried to render the home page I encountered errors so many times. Apparently, the reason I get those errors was my extreme lack of knowledge about useEffect
. You can check this documentation to understand it better or watch this video, or maybe that one. Below, I will share my errors, the reasons for those errors and explain how to solve that kind of problems when you see them too.
The first ever error I saw while I was trying to render the home page was this:
return (
<div>{blockNumber}</div>
<div>{block}</div>
)
The reason for the error is just the one above. It was my first day on this project and I was just starting to learn. Obviously, it was because I tried to render the whole object (block) which is impossible to do. In react, we can render object.keys, we can not render the whole object. That's probably the simplest thing for people who have even tiny knowledge about React :)) Whatever, I didn't have this knowledge at that time(3 days ago :D).
Okay, now move on. Let me try to render a value in an object, for example, the hash of the block. return ( <div>{block.hash}</div>)
Yep, that's it, it should render, right? Right? Nooooooo!
This screenshot is not exactly for that moment but the error is the same. Cannot read properties of undefined. What is going on? I have an object called block and I want to see the "block.hash". Why is it undefined? It's because of this: const [block, setBlock] = useState();
In this code, "block" is not defined. My idea during this time: "Okay, but even if it is not defined, inside of my useEffect function I will get the block object with API and set the value, so it should be defined, I don't get it."
Whatever, let's define it: const [block, setBlock] = useState({});
Can you see the difference? Inside the useState, "block" is defined as an empty object. Now it should work, right?
Oh, c'mon! Cannot read properties of null. There is still some error, but it's changing. Now it is not undefined, it is null. So, useState defined the "block" as an empty object and now we get the null error. Let's figure it out. Do you know the reason? You might know it. No? Yes? Any guesses? You're right, it's because of the useEffect!
As I said above, my lack of knowledge about useEffect was causing these errors. I didn't know the execution process. I learned that the functions inside of the useEffect hook are not executed when the first render occurred. That's how it works:
The page is rendered first with current values.
After the first render, functions inside the useEffect are executed and new values are set.
The page is rendered again with the new values.
That was it. That was extremely important and it was pretty basic actually. Not knowing this, not having enough knowledge about that core concept caused me hours but I'm glad I spent those hours. I learned by experiencing.
Alright, now I knew the issue, it's time to solve it. The page was rendering with undefined or null values. So I had to implement something. I first taught "Is it possible to execute useEffect before rendering?". That was the first thing that came to my mind. But it turns out it is not possible :/ So if I can not execute useEffect before rendering, can I stop or delay the first rendering? Or maybe render something else? It turns out there are some ways to solve this issue.
Do you know this: {something ? () : ()}
It is basically an if/else statement that we can use when rendering a page. Let me show:
return (
{!block ? (
<div> Loading... </div>
) : (
<div>{block.number}</div>
<div>{block.hash}</div>
)}
)
That's a simple example. If there is no block, it will render a page with the word "loading...", else it will render the block number and hash. So, that was the answer to my questions, my struggles, my errors and all.
But wait. What again? It's still the same error: Cannot read properties of null. I'm gonna be crazyyyy!!!
What's happening?
Check this out.
There is a trick, a tiny catch. Maybe you guessed it. In the code above !block
means if there is no block render "loading...". But in my code, there was a block. I defined it inside of the useState, remember? There WAS a block which was an empty object, so the !block
statement was false. It was still rendering the else part of the code and was giving the same errors. I could solve it by not assigning a value inside of the useState or I could write this: !block.hash
or !block.transactions
etc.
So, if you are going to use { ? () : ()}
, make sure that you didn't assign a value with useState or be careful about your statement.
Wow, that was long, there were too many errors. After I finally managed to render a page without errors, now it was time to build the tables for the home page. I wrote tons of words and we didn't even create our first page yet. But don't worry, those were the main problems, from now on it is going to be just mapping arrays etc.
<tbody>
{(latestBlocks.map((block, i) => {
return (
<tr key={i}>
<td>Block <Link to={`/block/${block.number}`}>{block.number}</Link></td>
<td>Fee recipient <Link to={`/address/${block.miner}`}>{block.miner.slice(0, 10)}...</Link></td>
<td>{block.transactions.length} tx</td>
</tr>)
}))}
</tbody>
With this code above, we can create a row for every element in latestBlocks array and every row has 3 columns. You might notice that there are <Link> tags. It's actually the same as the anchor tag. You remember we installed react-router for our project and the <Link> tag is part of it. It will navigate the user to the "/block/: id" and "/address/:id" paths.
Creating other pages
Actually, there is nothing more after finally handling the home page. The rest of them are almost the same. Get some parameters, use them to get another thing, render pages with the parameters you get etc.
export function Block(){
const { id } = useParams();
const [block, setBlock] = useState();
useEffect(() => {
const getBlock = async () => {
const block = await alchemy.core.getBlockWithTransactions(parseInt(id));
setBlock(block);
}
getBlock();
}, [id]);
console.log(block);
There is only one thing I want to share here. We can not know what the user will type to the URL, or what the URL will be in our route paths but we probably need that value somewhere. const { id } = useParams();
is the way we do it. We can extract the value in the URL with useParams().
After handling all the pages I had a working block explorer with extremely ugly pages. After all, I didn't write any CSS for this project at that time, I just wanted it to work properly. When I finished the project it was time for a tiny little styling. I just installed react-bootstrap to shape the tables a little bit, and the buttons etc. It is still not pretty, but not that ugly too. That was, just fine-ish.
I want to thank the Alchemy team for this great bootcamp, and I also want to thank you for reading. It must be tiring to read all the way down here, it was a long post. Actually, many things can be written about other parts of the project like getting transaction receipts or getting tokens of addresses etc but that's enough for a blog post. You can check the final shape of this project on my GitHub, and I would be glad if you want to share your thoughts or if you want to give some advice. See you next time :)
Take Home Messages (for myself :D)
Remember how the useEffect works. It will execute after the first render.
Be careful when using
{ ? () : ()}
. That simple thing caused you some trouble.Don't think the time you spent when learning the core concepts is wasted, it will add value.