Table of Contents
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)
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.
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.
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:
- Fetch all 151 Pokémon
- Loop through the list, fetching the Pokémon details for each Pokémon
- Extract the data we need from the Pokémon details
- Fetch the Pokémon’s effect using the extracted
id
from the details - 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.
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;
}
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).
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:
- We haven’t defined a
count
variable - 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 👇
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:
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 ✌️