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

mitey.titey
7 min readJan 16, 2023

--

In Part 1, I covered the core lesson of building a Next.js page that accepts user inputs, queries an API, and displays the results in a beautiful format.

At the end of this project we were left with a couple challenges that I will cover in this 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.

Adding a Copy icon

My first step was to create the copy button. Since we want the button to appear repeatedly next to every item in our gallery we need to add the object to our NFTCard component.

I wanted the icon to appear directly next to my contract address so within the same tag I inserted a button.

<p className="text-gray-600" >{`${nft.contract.address.substr(0,5)}...${nft.contract.address.substr(nft.contract.address.length-4)} `}
// Insert a button
<button I am a button ></button>
</p>

Next, I wanted to turn my button into an icon. I did some research and learned that to make an icon button I must first store the png image of the icon itself into my project directory. Using the icon link provided in the challenge I downloaded the smallest sized copy.png icon and saved it in the styles folder.

To reference the image from my copy button I created a class in the globals.css file. This class contained the URL of my image, copy.png,and the name used to reference it, copy-button. I also added properties such as width and height to size it appropriately.

#copy-button {
background-image: url('copy.png');
background-size: cover;
width: 16px;
height: 16px;
}

Now that my class is created I can reference in my button.

<p className="text-gray-600" >{`${nft.contract.address.substr(0,5)}...${nft.contract.address.substr(nft.contract.address.length-4)} `}
//Reference class with icon
<button id="copy-button"></button>
</p>

The final step is to handle the action when the user clicks the button. For this I created a new function that I called handleCopy . The function uses the built-in function navigator.clipboard.writeText, and for the parameter I passed the nft.contract.address in the same way I referenced it in the label.

const handleCopy = () => {
navigator.clipboard.writeText(nft.contract.address);
};

For the finishing touch, I thought it would be nice to give some indication to the user that the text had in fact been copied. To do this, I created a new Boolean variable named copied and set the value of that variable equal to true after my handleCopy function was complete.

    const [copied, setCopied] = useState(false);

const handleCopy = () => {
navigator.clipboard.writeText(nft.contract.address);
setCopied(true);
};

To display to the user, I read the value of copied and when it is true display the text “Copied!”.

<p className="text-gray-600" >{`${nft.contract.address.substr(0,5)}...${nft.contract.address.substr(nft.contract.address.length-4)} `}
<button onClick={handleCopy} id="copy-button"></button>
//Conditionally show Copied!
{copied ? <span> Copied!</span> : null}
</p>

Here is the final result. 🎉🎉

Add a Pagination system

To add a pagination system, is actually very similar to the steps needed to create the copy button. We first need to create the button, conditionally enable it when NFTs array is greater than 100 in size, and handle the action when the user clicks the button moving to the next page.

Since there will only be one button, I added a button to the index page underneath the code for NFTCards. I also styled it so that it would appear in the center of the page and look similar in size and padding to the NFT cards above it.

<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>
//Add button under the NFT Card and place it in the center of the page
<div className='flex flex-wrap gap-y-12 mt-4 w-5/6 gap-x-2 justify-center'>
<button>Next</button>

We need to conditionally enable the button just after we select Let’s Go! AND when the number of cards exceeds 100. We need it to be disabled when the page is initially loaded, and when no results or less than 100 results are returned.

In the API documentation it explains that a pageKey value will be returned if the API results exceed 100. I first stored this value so I can reference the length to turn my button on and off.

//define variable
const [page,fetchPage] = useState("")

if (nfts) {
console.log("nfts:", nfts)
setNFTs(nfts.ownedNfts)
//set value of variable upon return of result
fetchPage(nfts.pageKey)
}

To disable the button initially when the page is loaded we can add disabled to our class similar to what we did for the Let’s Go! button prior to anything being input into the address text fields. We add some other formatting in there while we are at it.

//Add button under the NFT Card and place it in the center of the page
<div className='flex flex-wrap gap-y-12 mt-4 w-5/6 gap-x-2 justify-center'>
<button className={"disabled:bg-slate-500 text-white bg-blue-400 px-4 py-2 mt-3 rounded-sm w-1/5"} >
Next
</button>

Then we can enable the button by changing the disabled property to the Boolean of page.length == 0. When nft.pageKey is blank indicating our API result is less than 100 or no results have been returned then the button will remain disabled.

//Add button under the NFT Card and place it in the center of the page
<div className='flex flex-wrap gap-y-12 mt-4 w-5/6 gap-x-2 justify-center'>
<button className={"disabled:bg-slate-500 text-white bg-blue-400 px-4 py-2 mt-3 rounded-sm w-1/5"}
disabled = {page.length == 0}
> Next
</button>

Now that we have properly enabled and disabled the button, now we must program it do something when selected. The first step is to add an onclick property.

<button className={"disabled:bg-slate-500 text-white bg-blue-400 px-4 py-2 mt-3 rounded-sm w-1/5"} 
disabled = {page.length == 0}
// Add onclick property
onClick={
() => {
}
}>Next</button>

Next, we head back to the API to learn how to fetch the next 100 results. To do this in getNFTs API must provide the pageKey value into the URL along with our original address and/or collection.

getNFTs API docs

For getNFTsForCollection, we must pass a startToken to the URL and the result will be nextToken.

getNFTsForCollection API docs

This is really easy since I already have the page variable collecting the value. So I headed to my functions that call the API and insert the &pageKey=${page} parameter similar to how we handle wallet and collection.

// fetchNFTs no collection
const fetchURL = `${baseURL}?owner=${wallet}&pageKey=${page}&pageSize=100`;
// fetchNFTs with collection
const fetchURL = `${baseURL}?owner=${wallet}&contractAddresses%5B%5D=${collection}&pageKey=${page}&pageSize=100`;
// fetchNFTsForCollection
const fetchURL = `${baseURL}?contractAddress=${collection}&withMetadata=${"true"}&nextToken=${page}&limit=100`;

To test the function I went through multiple iterations of the following steps. When I first open our page the Next button should be disabled.

If we perform a search with over 100 results, those will be displayed and the Next button will be enabled with item 100 being shown.

When we click the Next button, the next 100 results will appear, and 200 should be showing.

🎉🎉 Now showing items 101–200

That completes both challenges. 🎉 It was challenging, but the final working result was very rewarding.

Thank you for reading and as always I appreciate any feedback or questions that you may have. 🙏

My code can be found HERE.

Some remaining questions that I had.

  • How do I get the page URL to clear out if a new Address or collection is entered? I tried to add the code fetchPage(“”) in the onclick event for the Let’s Go! button prior to fetching new results, but that did not work. 😒
  • If I am on the third page how do I get back to the second? The pageKey only handles Next 100 and not Previous 100. 👈 Back please.

--

--