Road to Web 3 — NFT Gallery — Reflection Part 2 of 2
--
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
- Add an icon next to the NFT addresses to make it easy for people viewing your site to copy the contract address.
- 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.
For getNFTsForCollection
, we must pass a startToken
to the URL and the result will be nextToken
.
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.
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.