How To Use JavaScript Fetch API With Pokémon Example

by kleamerkuri
Javascript fetch API Pokemon project.

Who’s ready to embark on an exciting journey into the realm of Pokémon using the power of JavaScript and the Fetch API? 🙋‍♀️

In this tutorial, we’ll explore how to fetch data from the free Pokémon API and create a captivating sample project.

Get ready to unleash your inner Pokémon trainer as we construct a futuristic theme deck featuring the beloved original 151 Pokémon, all brought to life through an interactive slideshow experience.

Hey! See the finished product in action on CodePen. Note: The code was updated for an upcoming follow-up post to advance this baby with mobile swipe navigation and improved user experience 🙌

Our objective

The Fetch API is a modern JavaScript feature that simplifies making HTTP requests and handling responses. It allows us to retrieve data from external sources, such as APIs, effortlessly.

With the Pokémon API at our fingertips, we’ll harness its vast collection of information to curate a captivating project that both Pokémon enthusiasts and JavaScript developers will love.

Our goal is to create an immersive theme deck that transports us to a futuristic Pokémon world. By combining the powerful capabilities of the Fetch API, dynamic JavaScript, and captivating visuals, we’ll bring each Pokémon to life!

Throughout this blog post, we’ll dive into the core concepts of the Fetch API, including making GET requests, handling responses, and leveraging the retrieved data to construct our Pokémon slideshow deck.

We’ll also explore how to work with asynchronous code using Promises, enabling us to seamlessly retrieve and display Pokémon information.

Setting up the HTML structure

Okay, the title is somewhat of a lie – there’s not much HTML to set up 😅

Most of the HTML markup we’ll generate using JavaScript. There’s absolutely no reason for you to hardcode 151 cards, there are far more interesting things to do in life.

All we really need in our HTML is a parent wrapper to contain the Pokémon cards along with the next and previous buttons.

<div id="cards-container">
  <svg
  class="svg-right"
  viewBox="0 0 24 24"
  fill="none"
  xmlns="http://www.w3.org/2000/svg"
>
  <path
    d="M10.5858 6.34317L12 4.92896L19.0711 12L12 19.0711L10.5858 17.6569L16.2427 12L10.5858 6.34317Z"
    fill="currentColor"
  />
</svg>
  <svg
  class="svg-left"
  viewBox="0 0 24 24"
  fill="none"
  xmlns="http://www.w3.org/2000/svg"
>
  <path
    d="M10.5858 6.34317L12 4.92896L19.0711 12L12 19.0711L10.5858 17.6569L16.2427 12L10.5858 6.34317Z"
    fill="currentColor"
  />
</svg>
</div>

In my case, I opted for svg arrow icons with some modifications. Mainly, I removed the designated width and height from either svg, replacing them with two classes.

Hint: We’ll use the classes to apply styles later on! In fact, I’ll be assigning plenty of classes on our JS constructed HTML elements.

Fetching Data from the Pokémon API

While referencing the Pokémon API documentation, note how each endpoint of the request URL provides different sorts of data.

This means we’ll probably need to fetch from more than a single endpoint to meet our needs.

First, before heading into any sort of data collection, it’s good practice to understand the information you’re trying to collect.

If we don’t know what we want to display for each of the Pokémon, then all those request URLs turn out to be an overwhelming mess.

To prevent that from happening, let’s decide what info we want to show on the Pokémon card. I want to display the Pokémon:

  • name (in title case)
  • image (from the front)
  • ability (just one, I’m trying to keep the card simple)
  • effect (condensed version)
Pokemon card layout drawing.

Now that we know what to look for, time to fetch!

Highly recommended read 👉 14 ES6 Features You Need To Know To Refresh Your JavaScript. We go over the fetch format, async / await, and more!

1) Get all original Pokémon

const fetchPokemon = async () => {
  const response = await fetch('https://pokeapi.co/api/v2/pokemon?limit=151');
  const  data = await response.json();
  return data;
}

fetchPokemon delivers the name and Pokémon-specific URL for each of the 151 Pokémon.

Get Pokemon from API fetch.

2) Get the Pokémon details

const fetchPokeData = async (pokemon) => {
  const response = await fetch(pokemon.url);
  const data = response.json();
  return data;
}

fetchPokeData accepts a single pokemon as an argument and uses the Pokémon-specific URL to fetch the data for that specific Pokémon.

Fetch Pokemon details from API.

This API endpoint, however, doesn’t provide the effect. Looking at the documentation, the effect appears to be a property found in the return of the /ability/ endpoint.

So, one more step to get the effect 👇

3) Get the Pokémon’s effect

const fetchPokeAbility = async (id) => {
  const response = await fetch(`https://pokeapi.co/api/v2/ability/${id}/`);
  const data = response.json();
  return data;
}

fetchPokeAbility gets the data containing the effect of the specified Pokémon using the id property from the return object of fetchPokeData.

As a little side note, the API documentation suggests you can use either a Pokémon id or name with the /ability/ endpoint. However, using a name didn’t work out for me hence the use of id.

Note 👀
The fetch functions require logical loops and conditionals to retrieve the data we want for display. So far, all they do is get the data from the API. In turn, we need to get the data from the response data to mold into our display data 💁‍♀️

Handling the fetch response

I’ll be working with promises using async and await to parse and manipulate the data fetched from the API.

Hey!
For this simple example project, I will not employ error handling or take into consideration network issues. We’ll be keeping sophistication at a limit but know that, on a real-world application, you need to handle errors appropriately especially when fetching data!

Our fetch action is going to happen inside of a self-executing async function.

(async () => {
  const getPokemon = await fetchPokemon();
  // console.log(getPokemon) { results: [{}]}
  for( let poke of getPokemon.results) {
    let pokeDetails = await fetchPokeData(poke);  
    let { 
      id,
      name: pokeName,
      abilities: [
        { 
          ability: {
            name:ability
          }
        }
      ],
      sprites: { 
        other: {
          "official-artwork": {
            front_default: img
          }
        }
      }
    } = pokeDetails;

    let pokeAbility = await fetchPokeAbility(id);
    let { effect_entries: effects } = pokeAbility;
    let effect;
    effects.forEach( ef => {
      ef.language.name === 'en' && (effect = ef.short_effect);
    });

    pokemonList.push({
      pokeName, 
      ability,
      effect,
      img
    });
  }

});
})();

Breaking down the code:

  1. Fetch all 151 Pokémon
  2. Loop through the list, fetching the Pokémon details for each Pokémon
  3. Extract the data we need from the Pokémon details
  4. Fetch the Pokémon’s effect using the extracted id from the details
  5. Add the extracted data object into our pokemonList array

Tip: Loop through the effects to select the appropriate language! Keep a vigilant eye for the data structure of each property as some are lists while others are objects.

Custom Pokemon data to display.
Custom API data objects without effect

Displaying fetched data on the web page

Time to add some wicked cool styles to display our fetched data.

I’ll start by defining some basic layout and color styles:

:root {
  --main-blue: #0D1C30;
  --bright-blue: #ACDDF0;
  --hover-blue: rgba(108, 146, 172, .6);
  --body-blue: #294B61;
}

html {
  box-sizing: border-box;
}

*, *:before, *:after {
  box-sizing: inherit;
}

body {
  margin: 0;
  padding: 0;
  height: 100vh;
  background: var(--main-blue);
  font-family: 'Open Sans', sans-serif;
}

#cards-container {
  width: 100%;
  height: 100%;
  position: relative;
}

Follow with the positioning of the arrow icons that serve as the next and previous buttons.

svg {
  position: absolute;
  top: 40%;
  color: var(--hover-blue);
  box-shadow: 0px 0px 5px rgba(108, 146, 172, .3);
  width: 50px;
  transition: width .5s ease; 
}
.svg-right {
  right: calc(50% - 315px/2 - 100px);
}
.svg-right:hover {
  transform: scale(1.1);
}
.svg-left {
  transform: rotate(180deg);
  left: calc(50% - 315px/2 - 100px);
}
.svg-left:hover {
  transform: rotate(180deg) scale(1.1);
}
svg:hover {
  color: #32C2F0;
  cursor: pointer;
}
Styling the next and previous buttons.

Create Pokémon cards

Next, let’s generate the Pokémon cards by constructing a JS function since all cards have the same characteristics and are repeated.

Each card will have a(n):

  • header displaying the Pokémon name
  • image container displaying the Pokémon
  • body consisting of the Pokémon’s ability and effect
  • footer composed of a count out of 151

Header

const cardHead = (name) => {
  const head = document.createElement('div'),
        title = document.createElement('span');

  head.classList.add('card-head');
  title.classList.add('card-title');
  let name2 = name.charAt(0).toUpperCase() + name.slice(1);
  title.innerText = name2;

  head.appendChild(title);
  return head;
};

cardHead takes the Pokémon’s name and uses a combination of array methods to capitalize the first letter.

The return value is the head HTML element with the corresponding child elements for the name display and styles.

Image

const cardImg = (url) => {
  const sprite = document.createElement('div'),
        img = document.createElement('img');

  img.setAttribute('src', url);
  sprite.classList.add('card-img');

  sprite.appendChild(img);
  return sprite;
};

cardImg creates a wrapper and attaches an image of the Pokémon to it. The accepted value is the sprite URL from the fetched Pokémon details.

Choose a sprite from a selection of Pokémon images – I opted for a frontal image of the official artwork selection.

Body

const cardBody = (ab, ef) => {
  const body = document.createElement('div'),
        abP = document.createElement('p'),
        abE = document.createElement('p');

  let ab2 = ab.charAt(0).toUpperCase() + ab.slice(1);
  abP.innerText = ab2;
  abP.classList.add('poke-ability');
  abE.innerText = ef;
  abE.classList.add('poke-effect');

  [abP, abE].forEach( elm => body.appendChild(elm));

  body.classList.add('card-body');

  return body;
};

cardBody takes an ability and effect, modifies the ability capitalization, and attaches them to a body element.

Tip: Use a loop to append the two children elements to the body to avoid repetition!

More loops 👉 How To Use JavaScript Loops With A BTS Example

Footer

const cardFoot = (count) => {
  const foot = document.createElement('div'),
        span1 = document.createElement('span'),
        span2 = document.createElement('span');

  span1.innerText = `${count}/151`;
  span2.innerHTML = `By <a href="https://thehelpfultipper.com/" target="_blank" rel="noopener">THT</a>`; 
  span2.classList.add('creator');

  [span1, span2].forEach( elm => foot.appendChild(elm));

  foot.classList.add('card-foot');

  return foot;
};

cardFoot takes the count and displays it in the bottom left-hand corner. The bottom right is reserved for the creator’s name (aka the poke-awesome THT 😉).

Notice that the count is a dynamic variable while the total, 151, is hardcoded. If it was a case that we didn’t stick to the original 151 Pokémon, you’d be better off using a dynamic variable for the total using the array length.

Card

const createCard = (pokemon, count) => {
  let {
      pokeName, 
      ability,
      effect,
      img
    } = pokemon;

  const card = document.createElement('div'),
        head = cardHead(pokeName), 
        pokeimg = cardImg(img),
        body = cardBody(ability, effect),
        foot = cardFoot(count);

  card.classList.add('card');

  [head, pokeimg, body, foot].forEach( elm => card.appendChild(elm));

  cardsContainer.appendChild(card);

}

Finally, createCard takes the pokemon object and a count then puts all the other function constructors at work!

A few things that happen are:

  • destructuring the pokemon properties for simplicity’s sake
  • executing the card parts from the pre-defined functions
  • appending all the card parts to the card (note how a loop makes life easier!)

Adding card styles

Of course, we’re not getting far if we don’t define the styles for all those classes we assigned.

.card {
  width: 315px;
  height: 420px;
  border: 1px solid var(--hover-blue);
  border-radius: 6px;
  box-shadow: 0px 1px 6px rgba(108, 146, 172, .3);
/*   margin: 0 auto; */
  position: relative;
  top: 15%;
  left: calc(50% - 315px/2);
}

.card-head {
  padding: 20px;
  position: relative;
}

.card-head:after {
  content: '';
  width: 100px;
  height: 2px;
  position: absolute;
  bottom: 10px;
  right: 0;
  background: var(--body-blue);
}

.card-title {
  color: var(--bright-blue);
  font-size: 1.105rem;
  font-weight: 600;
}

.card-img {
  width: 60%;
  max-width: inherit;
  margin: 0 auto;
}

.card-img img {
  width: 100%;
}

.card-body {
  padding: 0 20px;
  color: #32C2F0;
  text-align: center;
}

.poke-ability {
  color: #CCC6F0;
  display: inline-block;
  margin-bottom: 0;
  position: relative;
}

.poke-ability:before, 
.poke-ability:after {
  content: '\2727';
  position: absolute;
  color: var(--bright-blue);
  font-size: .809rem;
  margin-top: 2px;
}

.poke-ability:before {
  left: -25px;
}
.poke-ability:after {
  right: -25px;
}

.poke-effect {
  font-size: .825rem;
}

.card-foot {
  color: #4189A8;
  width: 100%;
  border-top: 1px dotted var(--body-blue);
  position: absolute;
  bottom: 0;
  padding: 5px 15px;
}

.creator {
  float: right;
  font-size: .89rem;
}

.creator a {
/*   text-decoration: none; */
  color: inherit;
}

Now execute createCard(pokemonList[0]) to render the first item of the list (inside of the async function).

Styled Pokemon card no count.

Beautiful 🥲

But notice how the count is currently undefined – in fact, we have “undefined/151” which isn’t what we’re going for.

An undefined count is mainly due to two reasons:

  1. We haven’t defined a count variable
  2. We’re not incrementing count since we’re only rendering one card

To fix, define a count variable and iterate over the pokemonList array like so:

pokemonList.forEach( (poke, count) => {
        count++;
    createCard(poke, count);   

  });

Creating a Pokémon cards slideshow

As we bring this project to a close, our next challenge is creating a slideshow from the long list-like render of cards on display.

The goal is for a single card to display from a deck of cards. Like this 👇

Creating a deck of Pokemon cards.
Note the displayed card is number 60 out of 151

Clicking on either of the arrow buttons will either move you to the next card or the previous one.

To do this, I’ll define a variable, cardIndex, and a function, showCards.

let cardIndex = 1;

const showCards = (n) => {
  let cards = document.querySelectorAll('.card');
  n > cards.length && (cardIndex = 1);
  n < 1 && (cardIndex = cards.length);
  for( let i=0; i<pokemonList.length; i++) {
    cards[i].style.display = 'none';
  }
  cards[cardIndex-1].style.display = 'block';
}

showCards accepts a number (based on the cardIndex) and determines the display of the cards.

cardIndex is merely a reference to the card on display so I initialize with a value of one to represent the first card of the deck.

If the number, n, is greater than the length of the list of cards, the first card displays. Else, if n is less than one, the last card displays.

To start off, only the first card displays so executing showCards(cardIndex) gives us:

First pokemon card.

Lookin’ good but we’re kinda stuck in that we can’t really get to any of the cards since cardIndex doesn’t change 😒

We need to give functionality to the arrow buttons. They’re going to control the value of cardIndex and, through it, the Pokémon card in display.

const nextCard = (n) => {
  // n >> increment
  showCards(cardIndex += n);
}

nextCard accepts a number that acts as the increment and simply increments the cardIndex as it’s passed into showCards.

Let’s listen to the click event on the arrow icons to execute the newly defined nextCard.

document.querySelectorAll('svg').forEach( btn => {
    btn.addEventListener('click', () => {
      btn.classList.contains('svg-right') && nextCard(1);
      btn.classList.contains('svg-left') && nextCard(-1);
    });
  });

Extra: Animating the Pokémon

Huh, you thought I was done right?

Well, you’ll be sorely disappointed because I’ve still got one little trick up my sleeve to take this entire thing up a notch 😌

Before calling it a wrap, I’d like to add some subtle animation to the Pokémon image to create the illusion of motion.

Think of it as the Pokémon preening on their own stage 💁‍♀️

How do we fake motion when dealing with a flat image? We use CSS animation!

With a single animation, we can get Charizard to show off his wings (…very, very subtly).

@keyframes show-off {
  25% {
    transform: rotateY(10deg);
  }
  100% {
transform: rotateY(-5deg);
  }
}

Wrapping up

In this blog post, we used the power of JavaScript’s fetch to build a futuristic-themed deck of Pokémon cards.

We saw how to GET data from an API, extract it, and use some of that data to fetch even more specific data from other API endpoints.

I hope you now feel more confident with fetch because there’s a very big React project coming up our way that will fetch data from Unsplash 👀 

Be on the lookout, I need y’all to be prepped and ready for it! In the meantime, all source code from today is available on GitHub.

‘Till the next one, stay strong ✌️

Related Posts