Road to Web 3 — NFT Gallery — Reflection Part 1 of 2

mitey.titey
8 min readJan 14, 2023

--

For this week’s lesson, we were challenged to build an NFT gallery that allowed a user to input a combination of wallet address and/or contract address to display the corresponding NFT’s.

To accomplish this we used the Next.js framework and Tailwindcss for styling.

Overall, this was my favorite project, but also the most challenging. This lesson focused mostly on JavaScript and since I am beginner, many of the concepts were brand new. In writing this, I am definitely going to butcher some of the terminology, but hope to hit on the key concepts that made our project function.

The main parts of the project were:

  • Set-up the project folder
  • Collect the inputs from the user
  • Use Alchemy API to query for results,
  • Display the results in a gallery type format, and
  • Make everything look pretty.

Here is what the final result looked like.

To begin the project, we first had to set-up our project folder. Instead of hardhat that we have used in previous lessons, this time we used Next.js. To set-up the folder, we ran the following command in terminal.

// Create Project named nftGallery
npx create-next-app -e with-tailwindcss nftGallery

This created a number of folders and files in the directory most notably a pages and styles directory which contain our main page and corresponding libraries used to style it.

One of the cool things about Next.js is that you are able to “hot reload” as you are developing to see the results of your code changes instantly. We ran the following command in terminal, then opened a web browser and navigated to localhost:3000 to view our page.

// Start Local host
npm run dev

Next, we had to collect the inputs from the user by modifying the index.jsx file that displays as our main page.

Note: Originally, the index file was set to TypeScript(.tsx), but we flipped it over to JavaScript (.jsx).

To start, we cleared much of the initial code, and imported some libraries. Next, we added two input boxes and a checkbox using the following code. The type property sets what style of input (textbox, checkbox, dropdown, etc.) and the placeholder is the text that appears before the user interacts with the input object.

<div>
// Input boxes
<input type={"text"} placeholder="Add your wallet address"></input>
<input type={"text"} placeholder="Add the collection address"></input>
// Checkbox
<label><input type={"checkbox"}></input>Fetch for collection</label>
</div>

Next, we needed to be able to store the results of the user input in variables that we can use later to query our results. To do this, we create variables and set their values equal to the state of the page objects with useState. For the variables intended to be strings we use an empty string (useState[“”]), but for our checkbox which is a boolean we use useState(false).

In this code our variables are wallet, collection, and fetchForCollection and we set the values of those variables by calling setWalletAddress, setCollectionAddress, and setFetchForCollection respectively. We use the onChange property in our inputs to trigger the process.

// import useState library
import { useState } from 'react'

//Set variables
const [wallet, setWalletAddress] = useState("")
const [collection, setCollectionAddress] = useState("")
const [fetchForCollection, setFetchForCollection]=useState(false)

//Add onChange properties
<div>
// Input boxes
<input onChange={(e)=>{setWalletAddress(e.target.value); fetchPage("")}} value = {wallet} type={"text"} placeholder="Add your wallet address"></input>
<input onChange={(e)=>{setCollectionAddress(e.target.value); fetchPage("")}} value={collection} type={"text"} placeholder="Add the collection address"></input>
// Checkbox
<label><input onChange={(e)=>{; fetchPage("");setFetchForCollection(e.target.checked)}} type={"checkbox"}></input>Fetch for collection</label>
</div>

To test our code, we installed a React extension in Chrome and then used the Developer Tools menu to confirm our inputs were being collected. As you type on the page, the State: section would change.

React Developer Tools

Now that our inputs were set, and we were able to reference them through variables, now we needed to use the Alchemy API to fetch results.

Our first step was to create a button to initiate the search. Depending on what is selected from our inputs, different functions are called. If the checkbox is true then we call fetchNFTsForCollection, otherwise we call fetchNFTs. I will explain why there are different functions in the next few steps.

//  Button code
<button
onClick={
() => {
if (fetchForCollection) {
fetchNFTsForCollection();
}else
fetchNFTs();
}
}>Let's go! </button>

Next we need to create a variable, NFTs, to store the results of our query. This time we use [] inside of useState because the result of the query will be an array of information.

const [NFTs, setNFTs] = useState([])

Before we develop our functions, we first headed to Alchemy to create a new app and API Key similar to what we have done for previous projects.

Next, we needed to understand the URL structure of the different API’s available through Alchemy.

The first API, getNFTs, allows you to pass a combination of wallet address (required) and an optional contract address. The second API, getNFTsForCollection, allows you to pass a contract address (required) and return all the results in the NFT collection. The different API’s is the reason why we built separate functions to handle the inputs.

After understanding the different API URL’s we began building our functions. I will only cover fetchNFTs as the other function is very similar in construct.

First we name our function and set some basic variables, api_key, baseURL, and requestOptions, that are related to the structure of the API URL. Since the API Key is considered a “secret” you will have to get your own to use the code below.

const fetchNFTs = async() => {b
let nfts;
console.log("fetching nfts");
const api_key = "<Your API Key>"
const baseURL = `https://eth-mainnet.g.alchemy.com/v2/${api_key}/getNFTs/`;
var requestOptions = {
method: 'GET'
};

Next, we construct our API URL, fetchURL, which is different ddepending on whether the collection input has a value. After the URL has been defined we collect our result into, nft, by using the fetch function. The await prefix allows the result to be returned prior to moving to the next line of code.

if (!collection.length) {
const fetchURL = `${baseURL}?owner=${wallet}`;
nfts = await fetch(fetchURL, requestOptions).then(data => data.json())
} else {
const fetchURL = `${baseURL}?owner=${wallet}&contractAddresses%5B%5D=${collection}&pageKey=${page}&pageSize=100`;
nfts= await fetch(fetchURL, requestOptions).then(data => data.json())
}

If successful, we can see the results of our variable, nfts, in the console window of Developer Tools.

Now that we have successfully queried results, the next step was to display them on our main page. This gets a little dicey so just hang with me as I try to explain.

To display the results, we created a new component object called NFTCard. First we created a new folder in our project called Components, and added a blank file titled nftCard.jsx.

In a new file we create a Component called NFTCard which accepts a single parameter, nft. If you review the result in the console window in the previous step, you will see that each object in the array of nfts has properties associated (contract:, id:, title:, description:, etc).

The purpose of this component is to pass a single item from the nfts array and from its’ properties display the results one item at a time. For example, the nft title is displayed in the header by referencing nft.title, and the image is displayed as an image object by referencing the IPFS URL located in nft.media[0].gateway.

We can add functions to our results to clean up long strings (contract.address.substr), and even include hyperlinks by concatenating the base Etherscan address with the contract address stored at nft.contract.address.

export const NFTCard = ({ nft }) => {

return (
<div>
<div>
<img src={nft.media[0].gateway} ></img>
</div>
<div>
<div>
<h2>{nft.title}</h2>
<p>Id: {nft.id.tokenId.substr(nft.id.tokenId.length - 4)}</p>
<p>{`${nft.contract.address.substr(0,5)}...${nft.contract.address.substr(nft.contract.address.length-4)} `}
</p>
</div>
<div>
<p>{nft.description.substr(0,80)}</p>
</div>
<div>
<a target = "_blank" href={`https://etherscan.io/token/${nft.contract.address}`}>View on Etherscan</a>
</div>
</div>
</div>
)
}

The next step is to load the component in our main screen, but since it only handles one object at a time we must loop through all the objects in the NFTs array. Inside of the loop we reference our component NFTCard and pass the iterable nft as the parameter. Syntax wise this is a little tricky, but overall fairly simple once it all comes together.

<div>
{
//loop
NFTs.length && NFTs.map(nft => {
return (
//return component for single object
<NFTCard nft={nft}></NFTCard>
)
})
}
</div>

Up to this point we have focused mostly on function. The last step is to arrange the inputs and results in a format that is visually appealing.

To make everything look pretty, we used TailwindsCss. To apply styling we apply className definitions inside of our objects/components. To display multiple NFTCard components in clean rows and columns we add:

  • flex — allow the item to grow and shrink depending on page size
  • flex-wrap — wrap to the next row if you run out of space
  • gap-y-12 — set the gap between rows
  • mt-4 — add margins
  • w-5/6 — set the width as a percentage of the page
  • gap-x-2 — set the gap between columns
  • justify-center — align
    <div className='flex flex-wrap gap-y-12 mt-4 w-5/6 gap-x-2 justify-center'>
{
NFTs.length && NFTs.map(nft => {
return (
<NFTCard nft={nft}></NFTCard>
)
})
}
</div>

The className code can be added to every object, division, and button we have created. Here is how the inputs, and button are styled.

 <div className="flex flex-col items-center justify-center py-8 gap-y-3">
<div className="flex flex-col w-full justify-center items-center gap-y-2">
<input disabled={fetchForCollection} className="w-2/5 bg-slate-100 py-2 px-2 rounded-lg text-gray-800 focus:outline-blue-300 disabled:bg-slate-50 disabled:text-gray-50" onChange={(e)=>{setWalletAddress(e.target.value); fetchPage("")}} value = {wallet} type={"text"} placeholder="Add your wallet address"></input>
<input className="w-2/5 bg-slate-100 py-2 px-2 rounded-lg text-gray-800 focus:outline-blue-300 disabled:bg-slate-50 disabled:text-gray-50" onChange={(e)=>{setCollectionAddress(e.target.value); fetchPage("")}} value={collection} type={"text"} placeholder="Add the collection address"></input>
<label className="text-gray-600 "><input onChange={(e)=>{; fetchPage("");setFetchForCollection(e.target.checked)}} type={"checkbox"} className="mr-2"></input>Fetch for collection</label>
<button className={"disabled:bg-slate-500 text-white bg-blue-400 px-4 py-2 mt-3 rounded-sm w-1/5"}

That completes the project 🎉. We built a Next.js page that accepts user inputs, queries an API, and displays the results in a beautiful format .

Alchemy tutorial can be found HERE. My code can be found HERE.

At the end of this project we were left with some challenges. I will cover these in the next post.

Challenges

  1. Add an icon next to the NFT addresses to make it easy for people viewing your site to copy the contract address.
  2. Add a pagination system to view more than 100 NFTs, by using the pageKey parameter from the getNFTs endpoint.

--

--