Table of Contents
Hi, fellow developers—the Pokémons are back! Welcome to another exciting freeCodeCamp coding challenge. Today, we’re diving into the process of building fCC’s Pokémon Search App project.
DM maintains I’m notorious for always going a step beyond the project requirements. He’s right—I’m totally going to go a step or two further by implementing a user-friendly options list when searching for a Pokémon.
Hey! View the complete demo in CodePen 🔥
Don’t worry, I’ll bring y’all along with me so you can see how a simple feature makes a whole lot of difference 😀
Get ready to embark on an adventure where coding meets the captivating world of Pokémon.
Related: How To Use JavaScript Fetch API With Pokémon Example
Project intro
The Pokémon Search App project on freeCodeCamp is a fantastic opportunity to hone your web development skills while indulging in the Pokémon universe.
It’s a great way to demonstrate your understanding of working with API data in JavaScript.
As we progress through this project, we’ll cover various aspects, from setting up the header to displaying detailed Pokémon information dynamically.
Our objective is to build a Pokémon Search App that will search for Pokémon by name or ID and display the results to the user.
We’ll improve the provided fCC reference project by providing autosuggestions on search input (for those, like myself, who can’t figure out how to spell Pikatchu).
There’s no “t” in Pikachu, did you know? 🤯
In all, we’ll practice data retrieval and data display to front-end users.
So, grab your Poké Balls, and let’s get started!
Setting up the header
Every great app needs a distinctive and engaging header. For our Pokémon Search App, let’s create a header that sets the stage for the thrilling Pokémon adventure.
I’ll incorporate our lovely THT logo and an app title that captures the essence of the Pokémon universe.
JK, the app title screams out “Pokémon Search App”.
<header>
<div id="logo">
<img src="<https://thehelpfultipper.com/wp-content/uploads/2022/02/Untitled_design-removebg-preview-e1645316218147.png>" alt="The Helpful Tipper Logo">
</div>
<h1>Pokémon Search App</h1>
</header>
Adding the search input
Now, let’s tackle the core functionality of our app: the search input.
Users should be able to search for Pokémon by name or ID. The “or” isn’t optional as it’s a requirement to, you know, pass the test and get that certificate at the end.
<div class="search_wrapper">
<div class="search_input">
<span>Search for Pokémon Name or ID:</span>
<input type="text" id="search-input">
<div class="options"></div>
</div>
<button role="button" id="search-button">Search</button>
</div>
The goal is to implement a user-friendly search bar that responds dynamically as users type.
We’ll need to do a couple of things like:
- handle user input
- trigger the search functionality
To handle the user input, input events like click and keyup trigger callback functions that display the match options and filter for the search term.
// Filter matches based on user search
const filterOptions = () => {
const listItems = document.querySelectorAll(".options_item");
let filter = input.value.toLowerCase().trim();
for (let i = 0; i < listItems.length; i++) {
let txtVal = listItems[i].innerText.toLowerCase();
if (!txtVal.includes(filter) && filter !== "") {
listItems[i].classList.add("hide");
} else {
listItems[i].classList.remove("hide");
}
}
let hiddenItems = document.querySelectorAll(".options_item:not(.hide)");
if (hiddenItems.length === 0) {
options.classList.remove("show");
} else {
options.classList.add("show");
}
};
const showMatchOptions = () => {
let matches = getMatches(input.value, pokemonList);
matches.length > 0 && displayMatches(matches);
}
input.addEventListener("click", showMatchOptions);
input.addEventListener("keyup", filterOptions);
input.addEventListener("input", filterOptions);
The filter logic is very similar to that implemented in our previous project, How To Make Your Pokémon API Project Interactive.
For the search action, we’ll set the required search button as a trigger along with the keyboard Enter key.
btn.addEventListener("click", () => handleSearch(pokemonList));
document.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
handleSearch(pokemonList);
}
});
Note how both event listeners call the handleSearch()
function which contains the logic that will either display the Pokémon data or alert the user to search again.
// Perform search
const handleSearch = (pokemonList) => {
// Reset elements
resetAll();
// console.log('Search POKEMON');
const userInput = input.value.toLowerCase().trim();
// Get matches
let matches = getMatches(userInput, pokemonList);
// console.log('USER INPUT: ' + userInput);
const exactMatch = matches.some( item => {
return item.name === userInput;
});
if (!matches || matches.length === 0 || !userInput || !exactMatch) {
alert("No matches found. Try searching again!");
} else {
// Set meta
displayData(userInput);
}
};
Tip 👀
While fCC’s user story suggests using an alert as an error message, for a more polished and modern feel, consider implementing a visually appealing error message that seamlessly integrates with the app’s theme.
Dynamically displaying Pokémon data
Next, let’s dive into the dynamic display of Pokémon data fetched from the supplied Pokémon API.
When a user successfully searches for a Pokémon, display key information such as the Pokémon’s name and number, along with required details like weight, height, and an image of the Pokémon.
// Display Pokemon data
const displayData = async (pkm) => {
const singlePkm = await fetchSinglePokemon(pkm);
pkmName.innerHTML = `<strong>${toCap(singlePkm.name)}</strong>`;
pkmID.innerHTML = `#<strong>${singlePkm.id}</strong>`;
pkmH.innerHTML = `Height: ${singlePkm.height}`;
pkmW.innerHTML = `Weight: ${singlePkm.weight}`;
const img = document.createElement("img");
img.src = singlePkm.img;
imgWrapper.appendChild(img);
getTypesNames(singlePkm.types).forEach((type) => {
pkmTypes.innerHTML += `<span style="background:${
typeColors[type]
}; color:${getContrastColor(
typeColors[type]
)}">${type.toUpperCase()}</span>`;
});
setBaseStat(singlePkm.stats);
};
The displayData
function takes a single Pokémon (be it by name or ID) and fetches the particular details for that specific Pokémon using fetchSinglePokemon
.
// Get data for single Pokémon
const fetchSinglePokemon = async (pokemon) => {
// @pokemon: <name> or id
const resp = await fetch(`${apiURL}/${pokemon}`);
const data = await resp.json();
const {
name,
id,
height,
weight,
sprites: { front_default },
stats,
types
} = data;
return (filteredData = {
name,
id,
height,
weight,
img: front_default,
stats,
types
});
};
Note: I ✨slightly✨ customize the data that’s fetched for a single Pokémon, filtering out unnecessary data to simplify my work. For more on modifying API data, check out How To Build A Really Stunning Quote App With React.
A good first step for any developer is to familiarize yourself with the data structure. I highly recommend you know what the data is looking like before attempting to modify or extract.
For instance, the endpoint that provides a list of Pokémon returns an object with two properties: count
and results
.
// Response
{
"count": 1302,
"results": [
{
"id": 1,
"name": "bulbasaur",
"url": "<http://pokeapi-proxy.freecodecamp.rocks/api/pokemon/1/>"
},
{
"id": 2,
"name": "ivysaur",
"url": "<http://pokeapi-proxy.freecodecamp.rocks/api/pokemon/2/>"
}
Knowing this, I know my interest lies in extracting the results
array which itself comprises individual Pokémon objects.
You can’t assume with data my friends; you just gotta see it in action.
Styling with a futuristic Pokémon theme
Moving on, let’s focus on styling our Pokémon Search App by continuing along the lines of our existing futuristic Pokémon theme.
Incorporate sleek design elements, captivating colors, and playful animations to transport users into a visually immersive Pokémon experience. Whatever style you choose, it does NOT need to be futuristic 😅
I opted to infuse elements from my previous projects to maintain a cohesive theme across my portfolio. So make a tip out of that!
Adding specialty types with different colors
I liked how the types on the sample project had different background colors since I find it to be a styling strategy that enhances the visual experience for users.
So let’s differentiate specialty types with distinct colors in this project as well.
Of course, the question comes down to how we can make this distinction since type colors aren’t part of our dataset (unless I missed it).
Being a detail-oriented developer is a plus so we’ll go the extra step (yet again) of using the correct Pokémon type colors to represent each of the displayed types.
const typeColors = {
normal: "#A8A77A",
fire: "#EE8130",
water: "#6390F0",
electric: "#F7D02C",
grass: "#7AC74C",
ice: "#96D9D6",
fighting: "#C22E28",
poison: "#A33EA1",
ground: "#E2BF65",
flying: "#A98FF3",
psychic: "#F95587",
bug: "#A6B91A",
rock: "#B6A136",
ghost: "#735797",
dragon: "#6F35FC",
dark: "#705746",
steel: "#B7B7CE",
fairy: "#D685AD"
};
I’ll go even further by dynamically setting the font-color of the type based on the respective background color to keep it easily readable.
To achieve this, derive a function for calculating luminosity with the help of our AI friend ChatGPT 🤖
// Get contrast color
const getContrastColor = hexColor => {
// Convert hex color to RGB
const r = parseInt(hexColor.slice(1, 3), 16);
const g = parseInt(hexColor.slice(3, 5), 16);
const b = parseInt(hexColor.slice(5, 7), 16);
// Calculate relative luminance (brightness)
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
// Choose font color based on luminance
return luminance > 0.5 ? '#000000' : '#FFFFFF';
}
The function compares the brightness of the background color. If the background color is dark (i.e. value of luminance), set the font color to white; if it’s light, set the font color to a dark color.
Tip 👇
Be specific when utilizing AI as a coding partner. What you ask and how you compose your prompt plays a significant role in determining the quality of an answer. For instance, my question was: “I have a list of hex colors I’m using to set the background. Is there a way to designate the font color based on how dark these backgrounds are so it’s legible? So for dark hex colors, font should be white and for lighter ones it should be dark.”
And if you’re using AI, don’t forget to verify the response! Don’t trust blindly, verify its method by hitting Google to conduct your own mini research.
Completing the Base and Stats table
fCC’s project guidelines call for the creation of a Base and Stats table. However, it doesn’t have to be in a tabular format as long as you adhere to the required properties.
I’ll use the fetched data to populate the table, giving users a detailed breakdown of a Pokémon’s attributes.
<div class="meta_table">
<table>
<thead>
<tr>
<th scope="col">Base</th>
<th scope="col">Stats</th>
</tr>
</thead>
<tbody>
<tr>
<td>HP:</td>
<td id="hp"></td>
</tr>
<tr>
<td>Attack:</td>
<td id="attack"></td>
</tr>
<tr>
<td>Defense:</td>
<td id="defense"></td>
</tr>
<tr>
<td>Special-attack:</td>
<td id="special-attack"></td>
</tr>
<tr>
<td>Special-defense:</td>
<td id="special-defense"></td>
</tr>
<tr>
<td>Speed:</td>
<td id="speed"></td>
</tr>
</tbody>
</table>
</div>
Related: How To Create Simple HTML Tables (with examples)
Ensure that the table (if you’re creating one) is neatly formatted and aligns with the app’s overall design.
Wrapping up
We’ve successfully built freeCodeCamp’s Pokémon Search App, a dynamic and visually appealing addition to our web development portfolios.
This project not only hones our coding skills but also showcases our ability to create engaging user interfaces with API data.
As we continue our coding journey, remember that each project is a stepping stone toward mastery.
Going back to projects that revisit and cement basics, such as this project, we keep ourselves humble as we keep on growing and advancing.
The project source code can be found on this project’s GitHub repository.
Remember to submit your Pokémon Search App and share it with the freeCodeCamp and THT community.
Peace out ✌️