Mastering Fetch and Data Manipulation in JavaScript — Pokémon Edition 🎮
Welcome to a new adventure where we'll explore fetching data from APIs, manipulating it, and displaying it on the web, all with the exciting theme of Pokémon! We'll dive into real-world code examples, break down concepts, and engage with interactive tasks to solidify your understanding.
Introduction to Fetching Data
In the world of web development, fetching data from APIs is like a Pokémon trainer catching new Pokémon. You need to know where to look and how to handle what you find.
The Fetch API
The fetch
function allows you to make network requests similar to XMLHttpRequest
(XHR). It's promise-based and provides a cleaner, more concise syntax.
Basic Syntax:
fetch(url)
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error("Error:", error));
Task 1: Getting Pokémon Names
Goal: Fetch and display the names of all Pokémon from the PokéAPI.
Code Example:
function getPokemonNames() {
fetch("https://pokeapi.co/api/v2/pokemon?limit=1000")
.then((res) => res.json())
.then((data) => data.results.map((pokemon) => pokemon.name))
.then((names) => console.log(names));
}
getPokemonNames();
Explanation:
- Fetch Request: We request data from the PokéAPI endpoint.
- Response Parsing: Convert the response to JSON.
- Data Mapping: Extract the
name
property from each Pokémon. - Display: Log the array of names.
Task
- Modify the code to only fetch the first 150 Pokémon (Generation I).
- Display the names in the console.
Task
function getFirstGenPokemonNames() {
// Your code here
}
getFirstGenPokemonNames();
Solution
function getFirstGenPokemonNames() {
fetch("https://pokeapi.co/api/v2/pokemon?limit=151")
.then((res) => res.json())
.then((data) => data.results.map((pokemon) => pokemon.name))
.then((names) => console.log(names));
}
getFirstGenPokemonNames();
Task 2: Filtering Pokémon by Name
Goal: Get a list of Pokémon whose names start with the letter 'P'.
Code Example:
function getPokemonStartingWithP() {
fetch("https://pokeapi.co/api/v2/pokemon?limit=1000")
.then((res) => res.json())
.then((data) =>
data.results
.map((pokemon) => pokemon.name)
.filter((name) => name.startsWith("p"))
)
.then((names) => console.log(names));
}
getPokemonStartingWithP();
Explanation:
- Filtering: We use
.filter()
to select names starting with 'p'. - Case Sensitivity: Note that
.startsWith("p")
is case-sensitive.
Task
- Modify the filter to be case-insensitive.
- Hint: Convert names to lowercase before filtering.
Task
Modify the code to include case-insensitive filtering.
Solution
function getPokemonStartingWithP() {
fetch("https://pokeapi.co/api/v2/pokemon?limit=1000")
.then((res) => res.json())
.then((data) =>
data.results
.map((pokemon) => pokemon.name)
.filter((name) => name.toLowerCase().startsWith("p"))
)
.then((names) => console.log(names));
}
getPokemonStartingWithP();
Task 3: Sorting Pokémon by Name Length
Goal: Get the names of Pokémon sorted by their name length, from shortest to longest.
Code Example:
function getPokemonSortedByNameLength() {
fetch("https://pokeapi.co/api/v2/pokemon?limit=1000")
.then((res) => res.json())
.then((data) =>
data.results
.map((pokemon) => pokemon.name)
.sort((a, b) => a.length - b.length)
)
.then((names) => console.log(names));
}
getPokemonSortedByNameLength();
Explanation:
- Sorting: We use
.sort()
to compare the length of the names.
Task
- Modify the code to display the top 10 shortest Pokémon names.
Task
Limit the result to the top 10 shortest names.
Solution
function getTop10ShortestPokemonNames() {
fetch("https://pokeapi.co/api/v2/pokemon?limit=1000")
.then((res) => res.json())
.then((data) =>
data.results
.map((pokemon) => pokemon.name)
.sort((a, b) => a.length - b.length)
.slice(0, 10)
)
.then((names) => console.log(names));
}
getTop10ShortestPokemonNames();
Task 4: Displaying Pokémon Data in the DOM
Goal: Fetch and display the names and images of the first 20 Pokémon on the webpage.
Code Example:
<!DOCTYPE html>
<html>
<head>
<title>Pokémon Gallery</title>
<style>
.pokemon-container {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.pokemon-card {
width: 150px;
text-align: center;
}
.pokemon-card img {
width: 100%;
}
</style>
</head>
<body>
<div class="pokemon-container" id="pokemonContainer"></div>
<script>
function displayPokemon() {
fetch("https://pokeapi.co/api/v2/pokemon?limit=20")
.then((res) => res.json())
.then((data) => {
const pokemonPromises = data.results.map((pokemon) =>
fetch(pokemon.url).then((res) => res.json())
);
return Promise.all(pokemonPromises);
})
.then((pokemonData) => {
const container = document.getElementById("pokemonContainer");
container.innerHTML = pokemonData
.map(
(pokemon) => `
<div class="pokemon-card">
<img src="${pokemon.sprites.front_default}" alt="${pokemon.name}">
<h3>${pokemon.name}</h3>
</div>
`
)
.join("");
});
}
displayPokemon();
</script>
</body>
</html>
Explanation:
- Fetching Details: We fetch each Pokémon's data to get the image URL.
- Promise.all(): Waits for all fetch requests to complete.
- Displaying Data: We create HTML elements for each Pokémon.
Task
- Enhance the display by adding each Pokémon's type(s) under their name.
- Hint: Pokémon types are available in
pokemon.types
.
Task
Modify the code to include Pokémon types.
Solution
// Inside the .then((pokemonData) => { ... }) block
container.innerHTML = pokemonData
.map(
(pokemon) => `
<div class="pokemon-card">
<img src="${pokemon.sprites.front_default}" alt="${pokemon.name}">
<h3>${pokemon.name}</h3>
<p>Type: ${pokemon.types
.map((typeInfo) => typeInfo.type.name)
.join(", ")}</p>
</div>
`
)
.join("");
Task 5: Handling Errors
Goal: Implement error handling to display a message if the fetch request fails.
Code Example:
function displayPokemon() {
fetch("https://pokeapi.co/api/v2/pokemon?limit=20")
.then((res) => {
if (!res.ok) {
throw new Error("Network response was not ok");
}
return res.json();
})
.then((data) => {
// ... same as before
})
.catch((error) => {
console.error("Fetch error:", error);
document.getElementById("pokemonContainer").innerText =
"Failed to load Pokémon data.";
});
}
Explanation:
- Error Checking: We check if the response is OK before proceeding.
- Catch Block: Handles any errors during the fetch.
Task
- Simulate an error by changing the API URL to an incorrect one.
- Verify that the error message is displayed on the webpage.
Task
Change the URL to https://pokeapi.co/api/v2/pokemons?limit=20
and observe.
Common Pitfalls
Common Pitfalls
- Not Handling Promises Correctly: Forgetting to return promises or chaining
.then()
correctly. - Ignoring Errors: Not using
.catch()
can leave errors unhandled. - Inefficient Data Fetching: Making unnecessary network requests can slow down your app.
Conclusion
In this adventure, we've:
- Learned how to fetch data from an API using
fetch
. - Manipulated data with map, filter, and sort.
- Displayed data dynamically on a webpage.
- Handled errors gracefully.
Final Challenge: Pokémon Search Feature
Goal: Create a search input where users can type a Pokémon name and display matching Pokémon with their images and types.
Requirements:
- Input Field: Allow users to type a name.
- Live Search: Filter Pokémon as the user types.
- Display Results: Show matching Pokémon similar to Task 4.
Task
Implement the search feature in your existing code.
Solution
Here's a simplified version of how you might implement it:
<!-- Add this inside the body -->
<input type="text" id="searchInput" placeholder="Search Pokémon..." />
<script>
let allPokemonData = [];
function fetchAllPokemon() {
fetch("https://pokeapi.co/api/v2/pokemon?limit=1000")
.then((res) => res.json())
.then((data) => {
const pokemonPromises = data.results.map((pokemon) =>
fetch(pokemon.url).then((res) => res.json())
);
return Promise.all(pokemonPromises);
})
.then((pokemonData) => {
allPokemonData = pokemonData;
displayPokemon(pokemonData);
});
}
function displayPokemon(pokemonData) {
const container = document.getElementById("pokemonContainer");
container.innerHTML = pokemonData
.map(
(pokemon) => `
<div class="pokemon-card">
<img src="${pokemon.sprites.front_default}" alt="${pokemon.name}">
<h3>${pokemon.name}</h3>
<p>Type: ${pokemon.types
.map((typeInfo) => typeInfo.type.name)
.join(", ")}</p>
</div>
`
)
.join("");
}
document.getElementById("searchInput").addEventListener("input", (event) => {
const searchTerm = event.target.value.toLowerCase();
const filteredPokemon = allPokemonData.filter((pokemon) =>
pokemon.name.includes(searchTerm)
);
displayPokemon(filteredPokemon);
});
fetchAllPokemon();
</script>
Additional Resources
Happy coding, and may your JavaScript journey be as exciting as catching all the Pokémon! 🎉
Additional Exercises
Let's further test your skills with more exercises involving data manipulation and DOM rendering.
Exercise 1: Top 10 Heaviest Pokémon
Goal: Fetch the top 10 heaviest Pokémon and display their names and weights.
Task
- Fetch all Pokémon data.
- Sort the Pokémon by their weight in descending order.
- Display the top 10 heaviest Pokémon with their names and weights.
Task
Implement the code to achieve the above.
Solution
function getTop10HeaviestPokemon() {
fetch("https://pokeapi.co/api/v2/pokemon?limit=1000")
.then((res) => res.json())
.then((data) => {
const pokemonPromises = data.results.map((pokemon) =>
fetch(pokemon.url).then((res) => res.json())
);
return Promise.all(pokemonPromises);
})
.then((pokemonData) => {
const topHeaviest = pokemonData
.sort((a, b) => b.weight - a.weight)
.slice(0, 10)
.map((pokemon) => `${pokemon.name} - ${pokemon.weight}`);
console.log(topHeaviest);
});
}
getTop10HeaviestPokemon();
Exercise 2: Display Pokémon Abilities
Goal: For each Pokémon displayed on the webpage, include their abilities.
Task
- Modify your DOM rendering code to include abilities.
- Hint: Pokémon abilities are available in
pokemon.abilities
.
Task
Update the code to display abilities under each Pokémon.
Solution
// Inside the .then((pokemonData) => { ... }) block
container.innerHTML = pokemonData
.map(
(pokemon) => `
<div class="pokemon-card">
<img src="${pokemon.sprites.front_default}" alt="${pokemon.name}">
<h3>${pokemon.name}</h3>
<p>Type: ${pokemon.types
.map((typeInfo) => typeInfo.type.name)
.join(", ")}</p>
<p>Abilities: ${pokemon.abilities
.map((abilityInfo) => abilityInfo.ability.name)
.join(", ")}</p>
</div>
`
)
.join("");
Exercise 3: Pokémon Pagination
Goal: Implement pagination to display 20 Pokémon per page with navigation buttons.
Task
- Create buttons to navigate between pages.
- Fetch and display the corresponding Pokémon for each page.
- Ensure that the application doesn't fetch all Pokémon data at once to optimize performance.
Task
Implement pagination using the offset
and limit
query parameters in the API URL.
Solution
let currentPage = 1;
const limit = 20;
function displayPokemon(page) {
const offset = (page - 1) * limit;
fetch(`https://pokeapi.co/api/v2/pokemon?offset=${offset}&limit=${limit}`)
.then((res) => res.json())
.then((data) => {
const pokemonPromises = data.results.map((pokemon) =>
fetch(pokemon.url).then((res) => res.json())
);
return Promise.all(pokemonPromises);
})
.then((pokemonData) => {
// ... same DOM rendering code
});
}
// Add event listeners to navigation buttons
document.getElementById("nextButton").addEventListener("click", () => {
currentPage++;
displayPokemon(currentPage);
});
document.getElementById("prevButton").addEventListener("click", () => {
if (currentPage > 1) {
currentPage--;
displayPokemon(currentPage);
}
});
// Initial display
displayPokemon(currentPage);
You'll need to add the navigation buttons in your HTML:
<button id="prevButton">Previous</button> <button id="nextButton">Next</button>
Happy coding, and keep exploring the vast world of Pokémon through JavaScript! 🚀