Build a Pokémon Search App Project - Build a Pokémon Search App

Tell us what’s happening:

Im about to finish the last project for the Java Script course, and the only test I cant pass is the 14th. This test requires to have an alert(“Pokémon not found”) when the user clicks the search button with the input “Red”. When I try this, it works, but for some reason it still throws this error. It also gives this error: “[Error: TypeError: Cannot read properties of undefined (reading ‘trim’)]”. Any help?

Your code so far

<!DOCTYPE html>
<html lang="en">

  <head>

    <meta charset="utf=8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="stylesheet" href="styles.css">
    <title>Pokémon Search App</title>

  </head>

  <body>

    <h1>Pokémon Search App</h1>

    <div id="app-container">

      <form role="search" id="search-form">
        <label for="search-input">Search for Pokémon Name or ID:</label>
        <input type="text" name="pokemon" id="search-input" required/>
        <button id="search-button">Search</button>
      </form>

      <div id="pokemon-container">

        <div id="pokemon-presentation">
          <div id="pokemon-identity">
            <span id="pokemon-name"></span>
            <span id="pokemon-id"></span>
          </div>
          <div id="pokemon-size">
            <span id="weight"></span>
            <span id="height"></span>
          </div>
          <div id="pokemon-sprite"></div>
          <div id="types"></div>
        </div>

        <div id="pokemon-stats">
          <table>
            <tbody>
              <tr>
                <th class="left">Base</th>
                <th>Stats</th>
              </tr>
              <tr>
                <td class="left">HP</td>
                <td id="hp"></td>
              </tr>
              <tr>
                <td class="left">ATTACK</td>
                <td id="attack"></td>
              </tr>
              <tr>
                <td class="left">DEFENSE</td>
                <td id="defense"></td>
              </tr>
              <tr>
                <td class="left">Sp. ATTACK</td>
                <td id="special-attack"></td>
              </tr>
              <tr>
                <td class="left">Sp. DEFENSE</td>
                <td id="special-defense"></td>
              </tr>
              <tr>
                <td class="left">SPEED</td>
                <td id="speed"></td>
              </tr>
            </tbody>
          </table>
        </div>

      </div>

    </div>

    <script src="script.js"></script>

  </body>

</html>

//----------------------
//Defining the variables
//----------------------

const inputElement = document.getElementById("search-input");
const searchBtn = document.getElementById("search-button");

const nameElement = document.getElementById("pokemon-name");
const idElement = document.getElementById("pokemon-id");
const weightElement = document.getElementById("weight");
const heightElement = document.getElementById("height");
const spritesElement = document.getElementById("pokemon-sprite");
const typesElement = document.getElementById("types");

const baseStatsIds = [
  "hp",
  "attack",
  "defense",
  "special-attack",
  "special-defense",
  "speed"
];

const baseStatsElements = baseStatsIds.map((id)=>document.getElementById(id));

let validPokemons;







//-------------------------
//Creating useful functions
//-------------------------

//Returns either Pokemon(Object) or Undefined from pokemon list
const findPokemon = (input)=>{
  const cleanInput = input.replace(" ","").toLowerCase();

  const pokemon = validPokemons.find((pokemon) => (pokemon.id === parseInt(cleanInput)) || (pokemon.name === cleanInput));

  return pokemon;
}

//Sets pokemon stats into HTML document
const setPokemonHTML = (pokemon) => {
  const {
    height,
    id,
    name,
    sprites,
    stats,
    types,
    weight
  } = pokemon;

  nameElement.textContent = name.toUpperCase();
  idElement.textContent = "#" + id;

  weightElement.textContent = "Weight: " + weight;
  heightElement.textContent = "Height: " + height;
  
  spritesElement.innerHTML = `<img id="sprite" src="${sprites.front_default}"/>`;

  typesElement.innerHTML = "";
  types.forEach((el)=>{
    const type = el.type.name;
    typesElement.innerHTML += `<span class="type ${type}">${type.toUpperCase()}</span>`;
  })

  stats.forEach((stat,index)=>{baseStatsElements[index].textContent = stat.base_stat})
}

const clearPokemon = ()=>{
  nameElement.textContent = "";
  idElement.textContent = "";

  weightElement.textContent = "";
  heightElement.textContent = "";

  spritesElement.innerHTML = "";

  typesElement.innerHTML = "";

  baseStatsElements.forEach((baseStatElement)=>{baseStatElement.textContent = ""})
}

//Returns pokemon full stats
const getFullPokemon = async (url) => {
  try{
    const res = await fetch(url);
    const pokemonData = await res.json();
    setPokemonHTML(pokemonData);
  } catch(err){
    console.log(err);
  }
}

//Search function
const search = ()=>{
  const input = inputElement.value;
  const pokemon = findPokemon(input);
  if (!pokemon) {
    alert("Pokémon not found");
    clearPokemon();
    return;
  }
  getFullPokemon(pokemon.url);
}




//----------------------------------------------------
//Obtain the list of all pokemons at:
//https://pokeapi-proxy.freecodecamp.rocks/api/pokemon
//----------------------------------------------------

const fetchData = async ()=>{

  try{
    const res = await fetch("https://pokeapi-proxy.freecodecamp.rocks/api/pokemon");
    const data = await res.json();
    validPokemons = data.results;
    searchBtn.addEventListener("click",search);

  } catch(err){
    console.log(err);
  }

}

fetchData();





:root{

  --black: #07090F;
  --yellow: #A37C40;
  --white: #D6C3C9;
  --red: #98473E;
  --brown: #B49082;
  --black-shadow: #07090F8F;
  --yellow-shadow: #A37C408F;
  --white-shadow: #D6C3C98F;
  --red-shadow: #98473E8F;
  --brown-shadow: #B490828F;

}

html, *{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  text-decoration: none;
  list-style: none;
}

body{
  width: 100%;
  height: 100%;

  background-color: var(--black);
}

h1{
  color: white;
  font-family: arial;
  font-weight: 600;
  font-size: 3em;
  text-align: center;
  line-height: 3em;
}

#app-container{
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  align-items: center;
  gap: 15px;

  margin: auto;
  padding: 25px;

  width: 90%;
  max-width: 700px;

  border-radius: 5px;

  box-shadow: 0 0 10px var(--red-shadow);

  background-color: var(--red);
}

#search-form{
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  align-items: space-around;
  gap: 10px 50px;

  padding: 20px;

  width: 100%;
  
  border-radius: 5px;

  box-shadow: 0 0 20px #00000060;
  
  background-color: var(--brown);
}

#search-form label{
  width: 100%;

  font-family: arial;
  font-weight: 500;
  font-size: 1.7em;
  text-align: center;
}

#search-input{
  width: 50%;
  height: 40px;
  padding-left: 15px;
  font-family: arial;
  font-weight: 500;
  font-size: 1.4em;
  text-align: left;
}

#search-button{
  cursor: pointer;

  width: 20%;
  height: 40px;

  border: none;
  border-radius: 10px;

  box-shadow: 0 0 8px var(--black);

  background-color: var(--white);

  color: var(--black);
  font-family: arial;
  font-weight: 600;
  font-size: 1.3em;
  text-align: center;
}

#search-button:hover{
  box-shadow: 0 0 12px var(--black);

  background-color: #ffffff;
}

#search-button:active{
  box-shadow: 0 0 4px var(--black);

  background-color: var(--white);
}






#pokemon-container{
  display: flex;
  flex-direction: column;
  width: 100%;
  gap: 15px;
}





#pokemon-presentation{
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 10px 50px;

  padding: 20px;

  width: 100%;
  min-height: 350px;
  
  border-radius: 5px;

  box-shadow: 0 0 20px #00000060;
  
  background-color: var(--brown);
}

#pokemon-identity{
  display: flex;
  flex-direction: row;
}

#pokemon-name,#pokemon-id{
  font-family: arial;
  font-weight: 500;
  font-size: 1.7em;
  text-align: center;
}

#weight,#height{
  font-family: arial;
  font-weight: 500;
  font-size: 1.3em;
  text-align: center;
}



#pokemon-sprite{
  width: 30%;
  margin: auto;
}

#pokemon-sprite img{
  width: 100%;
  object-fit: cover;
  transform: scale(2.3);
}



.type{
  font-family: arial;
  font-weight: 500;
  font-size: 1.1em;
  text-align: center;
  padding: 5px;
  border-radius: 5px;
  box-shadow: 0 0 5px #0000008f;
}





#pokemon-stats{

  width: 100%;
  
  border-radius: 5px;
}

#pokemon-stats table, #pokemon-stats tbody{
  width: 100%;
}

#pokemon-stats tr{
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  width: 100%;
}

#pokemon-stats tbody{
  display: flex;
  flex-direction: column;
  gap: 10px;
}

#pokemon-stats th{
  padding: 20px 0;

  box-shadow: 0 0 20px #00000060;
  
  background-color: var(--brown);

  font-family: arial;
  font-size: 1.3em;
  font-weight: 600;
  text-align: center;
}

#pokemon-stats td{
  padding: 13px 0;

  box-shadow: 0 0 20px #00000060;
  
  background-color: var(--brown);

  font-family: arial;
  font-size: 1.3em;
  font-weight: 400;
  text-align: center;
}

#pokemon-stats .left{
  width: 63%;
}

#pokemon-stats tr :not(.left){
  width: 34%;
}

.normal {
  background-color: #b7b7aa;
}

.fire {
  background-color: #ff6f52;
}

.water {
  background-color: #42a1ff;
}

.electric {
  background-color: #fecc33;
}

.grass {
  background-color: #78cc55;
}

.ice {
  background-color: #66ccfe;
}

.fighting {
  background-color: #d3887e;
}

.poison {
  background-color: #c68bb7;
}

.ground {
  background-color: #dfba52;
}

.flying {
  background-color: #8899ff;
}

.psychic {
  background-color: #ff66a3;
}

.bug {
  background-color: #aabb23;
}

.rock {
  background-color: #baaa66;
}

.ghost {
  background-color: #9995d0;
}

.dragon {
  background-color: #9e93f1;
}

.dark {
  background-color: #b59682;
}

.steel {
  background-color: #abaabb;
}

.fairy {
  background-color: #ed99ed;
}

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.0.0.0

Challenge Information:

Build a Pokémon Search App Project - Build a Pokémon Search App

Hi there!

Ensure inputElement.value is defined and properly trimmed before processing in findPokemon.
In the search function, ensure inputElement.value is non-empty and properly trimmed before proceeding.

Here in the input initialization, you can use ternary operator to, if the condition checks inputElement.value, return inputElement.value with trim(), otherwise return an empty strings. This ensures that if inputElement.value is undefined, it defaults to an empty string, avoiding the trim error.
This also gives an additional alert if the input is empty.

Try searching first for pokemon that exist, and then for one that doesn’t exist. During the second search data from first search is still displayed.

Tried it, still doesnt work :(. Im thinking that maybe, as I added the eventListener in the Async function, when it starts to test it, the button does nothing as its loading the content. That will lead me to remake everything, and fetch the pokemons every time you click the button. //Search function const search = ()=>{ const input = inputElement.value ? inputElement.value.trim() : ""; const pokemon = findPokemon(input); if (!pokemon) { alert("Pokémon not found"); clearPokemon(); return; } getFullPokemon(pokemon.url); }
If i did something wrong tell me and I change it. I will remake everything tomorrow. Thanks!

Its actually cleared by the clearPokemon() function

But after the search…

Well, in the “Functionally Similar” app from fCC, thats how it works. It first gives the alert and then clears

Huh, it appears to not clear before alert. What’s interesting is that in code of the demo project, clearing is actually done before alert. Looks like browser just renders that update after alert is closed.

Found the solution, but I dont think its a good one: I changed the code so that instead of fetching the pokemons before enabling the buttons, I enable the buttons from the start, and every time the user tries to search a pokemon, the code fetches the pokemons, but also waits in a while loop, (this is bad because if the fetch gives an error, the while loop would continue). If you want the code, here it is:

//----------------------
//Defining the variables
//----------------------

const inputElement = document.getElementById("search-input");
const searchBtn = document.getElementById("search-button");

const nameElement = document.getElementById("pokemon-name");
const idElement = document.getElementById("pokemon-id");
const weightElement = document.getElementById("weight");
const heightElement = document.getElementById("height");
const spritesElement = document.getElementById("pokemon-sprite");
const typesElement = document.getElementById("types");

const baseStatsIds = [
  "hp",
  "attack",
  "defense",
  "special-attack",
  "special-defense",
  "speed"
];

const baseStatsElements = baseStatsIds.map((id)=>document.getElementById(id));

let validPokemons;

//-------------------------
//Creating useful functions
//-------------------------

//Returns either Pokemon(Object) or Undefined from pokemon list
const findPokemon = (input)=>{
  const cleanInput = input.replace(" ", "").toLowerCase();
  return validPokemons?.find((pokemon) => 
    (pokemon.id === parseInt(cleanInput)) || 
    (pokemon.name === cleanInput)
  );
}

//Sets pokemon stats into HTML document
const setPokemonHTML = (pokemon) => {
  const {
    height,
    id,
    name,
    sprites,
    stats,
    types,
    weight
  } = pokemon;

  nameElement.textContent = name.toUpperCase();
  idElement.textContent = "#" + id;

  weightElement.textContent = "Weight: " + weight;
  heightElement.textContent = "Height: " + height;
  
  spritesElement.innerHTML = `<img id="sprite" src="${sprites.front_default}"/>`;

  typesElement.innerHTML = "";
  types.forEach((el) => {
    const type = el.type.name;
    typesElement.innerHTML += `<span class="type ${type}">${type.toUpperCase()}</span>`;
  });

  stats.forEach((stat, index) => {
    baseStatsElements[index].textContent = stat.base_stat;
  });
}

const clearPokemon = () => {
  nameElement.textContent = "";
  idElement.textContent = "";

  weightElement.textContent = "";
  heightElement.textContent = "";

  spritesElement.innerHTML = "";

  typesElement.innerHTML = "";

  baseStatsElements.forEach((baseStatElement) => {
    baseStatElement.textContent = "";
  });
}

//Returns pokemon full stats
const getFullPokemon = async (url) => {
  try {
    const res = await fetch(url);
    const pokemonData = await res.json();
    setPokemonHTML(pokemonData);
  } catch (err) {
    console.log(err);
  }
}

//Fetches and searches pokemon
const search = async () => {
  const input = inputElement.value ? inputElement.value.trim() : "";
  console.log(input + "input");
  const pokemon = await fetchData(input);
  console.log(pokemon);

  
  if (!pokemon) {
    clearPokemon();
    alert("Pokémon not found");
    return;
  }

  getFullPokemon(pokemon.url);
}

//----------------------------------------------------
//Obtain the list of all pokemons at:
//https://pokeapi-proxy.freecodecamp.rocks/api/pokemon
//----------------------------------------------------

const fetchData = async (input) => {
  try {
    const res = await fetch("https://pokeapi-proxy.freecodecamp.rocks/api/pokemon");
    const data = await res.json();
    validPokemons = await data.results;
    while(!validPokemons){
      
    }
    return findPokemon(input);
  } catch (err) {
    console.log(err);
  }
}

searchBtn.addEventListener("click", search);

When I played around with your code, I just moved the clearing at the start of the search function. Which was enough to make tests pass.

could you show me what you mean? I tried it and still didnt work

Found it!! The better solution :slight_smile:

removed

It is great that you solved the challenge, but instead of posting your full working solution, it is best to stay focused on answering the original poster’s question(s) and help guide them with hints and suggestions to solve their own issues with the challenge.

We are trying to cut back on the number of spoiler solutions found on the forum and instead focus on helping other campers with their questions and definitely not posting full working solutions.