Mastering Fetch and Data Manipulation in JavaScript β Global Explorer Edition π β
Welcome, fellow coder! Embark on a journey around the world as we explore how to fetch and manipulate data from the REST Countries API. We'll dive into real-world code examples, break down concepts, and engage with interactive tasks to solidify your understanding. Get ready to become a JavaScript globetrotter! βοΈ
Introduction to Fetching Data β
In the vast landscape of web development, fetching data from APIs is like exploring new countriesβyou need to know where to look and how to navigate the information 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) => {
// Handle the data
})
.catch((error) => console.error("Error:", error));
Task 1: Getting Country Names β
Goal: Fetch and display the names of all countries from the REST Countries API.
Code Example β
function getCountryNames() {
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) => countries.map((country) => country.name.common))
.then((names) => console.log(names));
}
getCountryNames();
Explanation β
- Fetch Request: We request data from the REST Countries API endpoint.
- Response Parsing: Convert the response to JSON.
- Data Mapping: Extract the
name.common
property from each country object. - Display: Log the array of country names.
Try It Yourself β
Task β
- Modify the code to display only the country names in uppercase.
Task
function getCountryNamesInUpperCase() {
// Your code here
}
getCountryNamesInUpperCase();
Answer
function getCountryNamesInUpperCase() {
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) =>
countries.map((country) => country.name.common.toUpperCase())
)
.then((names) => console.log(names));
}
getCountryNamesInUpperCase();
Task 2: Filtering Countries by Population β
Goal: Get a list of countries with a population greater than 100 million.
Code Example β
function getHighlyPopulatedCountries() {
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) =>
countries
.filter((country) => country.population > 100_000_000)
.map((country) => country.name.common)
)
.then((names) => console.log(names));
}
getHighlyPopulatedCountries();
Explanation β
- Filtering: We use
.filter()
to select countries with a population over 100 million. - Mapping: Extract the country names.
Try It Yourself β
Task β
- Modify the code to include the population in the output.
- Format:
"Country Name - Population"
Task
Update the .map()
method to include the population.
Answer
function getHighlyPopulatedCountriesWithPopulation() {
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) =>
countries
.filter((country) => country.population > 100_000_000)
.map((country) => `${country.name.common} - ${country.population}`)
)
.then((result) => console.log(result));
}
getHighlyPopulatedCountriesWithPopulation();
Task 3: Sorting Countries by Population β
Goal: Display countries with a population over 100 million, sorted by population in descending order.
Code Example β
function getSortedHighlyPopulatedCountries() {
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) =>
countries
.filter((country) => country.population > 100_000_000)
.sort((a, b) => b.population - a.population)
.map(
(country, index) =>
`${index + 1}. ${country.name.common} - ${country.population}`
)
)
.then((result) => console.log(result.join("\n")));
}
getSortedHighlyPopulatedCountries();
Explanation β
- Sorting: We use
.sort()
to order countries by population. - Indexing: Add ranking numbers to each country.
Try It Yourself β
Task β
- Modify the code to format the population numbers with commas for readability.
Task
Use Intl.NumberFormat
to format the population numbers.
Answer
function getFormattedSortedHighlyPopulatedCountries() {
const formatter = new Intl.NumberFormat("en-US");
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) =>
countries
.filter((country) => country.population > 100_000_000)
.sort((a, b) => b.population - a.population)
.map(
(country, index) =>
`${index + 1}. ${country.name.common} - ${formatter.format(
country.population
)}`
)
)
.then((result) => console.log(result.join("\n")));
}
getFormattedSortedHighlyPopulatedCountries();
Task 4: Displaying Countries in the DOM β
Goal: Display the top 10 most populous countries on the webpage in a list format.
Code Example β
<!DOCTYPE html>
<html>
<head>
<title>Top 10 Populous Countries</title>
<style>
.country-list {
list-style-type: decimal;
}
</style>
</head>
<body>
<h1>Top 10 Most Populous Countries</h1>
<ul class="country-list"></ul>
<script>
function displayTop10PopulatedCountries() {
const formatter = new Intl.NumberFormat("en-US");
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) =>
countries
.sort((a, b) => b.population - a.population)
.slice(0, 10)
.map(
(country) =>
`<li>${country.name.common} - ${formatter.format(
country.population
)}</li>`
)
.join("")
)
.then((result) => {
document.querySelector(".country-list").innerHTML = result;
});
}
displayTop10PopulatedCountries();
</script>
</body>
</html>
Explanation β
- Sorting and Slicing: Get the top 10 countries by population.
- DOM Manipulation: Insert the list items into the
<ul>
element.
Try It Yourself β
Task β
- Enhance the display by adding the country flags next to their names.
Task
Modify the .map()
method to include an <img>
tag with the country's flag.
Answer
.map(
(country) =>
`<li><img src="${country.flags.svg}" alt="${country.name.common} flag" width="20"> ${country.name.common} - ${formatter.format(country.population)}</li>`
)
Task 5: Displaying All Countries with Flags β
Goal: Display all countries with their flags and additional information on the webpage.
Code Example β
<!DOCTYPE html>
<html>
<head>
<title>All Countries</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: whitesmoke;
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
}
.country-card {
width: 200px;
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
}
.country-card img {
width: 100%;
height: 120px;
object-fit: cover;
}
.country-card h2 {
font-size: 1.2em;
margin: 0.5em 0;
}
.country-card p {
margin: 0.5em;
font-size: 0.9em;
}
</style>
</head>
<body>
<script>
function displayAllCountries() {
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) => {
const countryCards = countries
.map(
(country) => `
<div class="country-card">
<img src="${country.flags.svg}" alt="${
country.name.common
} flag">
<h2>${country.name.common}</h2>
<p><strong>Capital:</strong> ${
country.capital ? country.capital[0] : "N/A"
}</p>
<p><strong>Region:</strong> ${country.region}</p>
<p><strong>Population:</strong> ${new Intl.NumberFormat(
"en-US"
).format(country.population)}</p>
</div>
`
)
.join("");
document.body.innerHTML = countryCards;
});
}
displayAllCountries();
</script>
</body>
</html>
Explanation β
- Data Mapping: Create HTML templates for each country.
- Handling Undefined Values: Use conditional (ternary) operators to handle missing data.
Try It Yourself β
Task β
- Add interactivity by implementing a search bar to filter countries by name.
Task
- Create an
<input>
element for the search bar. - Add an event listener to filter the displayed countries as the user types.
Answer
Add the search bar:
<input
type="text"
id="searchBar"
placeholder="Search countries..."
style="width: 100%; padding: 1em; font-size: 1em;"
/>
Modify the script:
let countriesData = [];
function displayCountries(countries) {
const countryCards = countries.map(/* same as before */).join("");
document.body.innerHTML = countryCards;
}
function displayAllCountries() {
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) => {
countriesData = countries;
displayCountries(countriesData);
});
}
document.getElementById("searchBar").addEventListener("input", (e) => {
const searchTerm = e.target.value.toLowerCase();
const filteredCountries = countriesData.filter((country) =>
country.name.common.toLowerCase().includes(searchTerm)
);
displayCountries(filteredCountries);
});
displayAllCountries();
Additional Exercises β
Exercise 1: Grouping Countries by Region β
Goal: Display countries grouped by their region (Africa, Americas, Asia, Europe, Oceania).
Task β
- Group countries by their
region
property. - Display each group under a heading with the region name.
Task
- Use an object or
Map
to group countries. - Iterate over the groups to generate the HTML.
Solution
function groupCountriesByRegion() {
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) => {
const regions = {};
countries.forEach((country) => {
const region = country.region || "Others";
if (!regions[region]) {
regions[region] = [];
}
regions[region].push(country);
});
let htmlContent = "";
for (const [region, countries] of Object.entries(regions)) {
htmlContent += `<h2>${region}</h2><div class="region-group">`;
htmlContent += countries
.map(
(country) => `
<div class="country-card">
<img src="${country.flags.svg}" alt="${country.name.common} flag">
<h3>${country.name.common}</h3>
</div>
`
)
.join("");
htmlContent += `</div>`;
}
document.body.innerHTML = htmlContent;
});
}
groupCountriesByRegion();
Exercise 2: Implementing Pagination β
Goal: Display countries in pages, showing 20 countries per page.
Task β
- Implement pagination controls (Previous, Next buttons).
- Fetch and display the corresponding countries for each page.
Task
- Use array slicing to get the countries for the current page.
- Update the displayed countries when the page changes.
Solution
let countriesData = [];
let currentPage = 1;
const countriesPerPage = 20;
function displayCountries(countries) {
const startIndex = (currentPage - 1) * countriesPerPage;
const paginatedCountries = countries.slice(
startIndex,
startIndex + countriesPerPage
);
const countryCards = paginatedCountries.map(/* same as before */).join("");
document.getElementById("countriesContainer").innerHTML = countryCards;
}
function setupPagination(totalCountries) {
const totalPages = Math.ceil(totalCountries / countriesPerPage);
document.getElementById("pagination").innerHTML = `
<button onclick="changePage(-1)" ${
currentPage === 1 ? "disabled" : ""
}>Previous</button>
<span>Page ${currentPage} of ${totalPages}</span>
<button onclick="changePage(1)" ${
currentPage === totalPages ? "disabled" : ""
}>Next</button>
`;
}
function changePage(direction) {
currentPage += direction;
displayCountries(countriesData);
setupPagination(countriesData.length);
}
function displayAllCountries() {
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) => {
countriesData = countries;
displayCountries(countriesData);
setupPagination(countriesData.length);
});
}
displayAllCountries();
Add the necessary HTML:
<div id="countriesContainer"></div>
<div id="pagination"></div>
Exercise 3: Handling Errors Gracefully β
Goal: Implement error handling to display a user-friendly message if the fetch request fails.
Task β
- Simulate an error by using an incorrect API endpoint.
- Display an error message on the webpage.
Task
- Use
.catch()
to handle errors. - Update the DOM to show the error message.
Solution
function displayAllCountries() {
fetch("https://restcountries.com/v3.1/alls") // Incorrect endpoint
.then((res) => {
if (!res.ok) {
throw new Error("Network response was not ok");
}
return res.json();
})
.then((countries) => {
// ... same as before
})
.catch((error) => {
console.error("Fetch error:", error);
document.body.innerHTML =
"<h2>Failed to load country data. Please try again later.</h2>";
});
}
Exercise 4: Displaying Neighboring Countries β
Goal: For each country, display its neighboring countries.
Task β
- Fetch the
borders
property for each country. - Display the neighboring countries' names.
Task
- Use additional API calls to get the names of bordering countries.
- Be cautious of countries with no borders.
Solution
Due to the complexity, we can note that the borders
property provides country codes. We'll need to map these codes to country names.
function displayCountriesWithNeighbors() {
let countryCodeToName = {};
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) => {
countries.forEach((country) => {
countryCodeToName[country.cca3] = country.name.common;
});
const countryCards = countries
.map((country) => {
const neighbors = country.borders
? country.borders.map((code) => countryCodeToName[code]).join(", ")
: "No neighboring countries";
return `
<div class="country-card">
<img src="${country.flags.svg}" alt="${country.name.common} flag">
<h2>${country.name.common}</h2>
<p><strong>Neighbors:</strong> ${neighbors}</p>
</div>
`;
})
.join("");
document.body.innerHTML = countryCards;
});
}
displayCountriesWithNeighbors();
Common Pitfalls β
Common Pitfalls
- Forgetting to Handle Errors: Always include
.catch()
to handle network or parsing errors. - Not Using
return
in Promises: Ensure you return promises when chaining.then()
calls. - Overloading the DOM: Rendering too many elements can slow down the page. Consider pagination or lazy loading.
- Ignoring Undefined Values: Always check for undefined or null values to prevent runtime errors.
Conclusion β
In this global adventure, we've:
- Fetched data from the REST Countries API using
fetch
. - Manipulated data with array methods like
.map()
,.filter()
, and.sort()
. - Displayed data dynamically in the DOM.
- Handled errors gracefully.
- Enhanced interactivity with search and pagination.
Final Challenge: Interactive Country Explorer β
Goal β
Create an interactive web application where users can:
- Search for countries by name.
- Filter countries by region.
- View detailed information about each country, including flag, capital, population, languages, and currencies.
- Click on a country to view its neighboring countries.
Requirements β
- Responsive Design: Ensure the app works well on all screen sizes.
- Efficient Data Handling: Minimize API calls and optimize performance.
- User-Friendly Interface: Make it intuitive and visually appealing.
Task
Start by planning your application's structure. Consider using frameworks like React or Vue.js for a more scalable solution, or stick to vanilla JavaScript for simplicity.
Additional Resources β
Happy coding, and may your JavaScript journey take you to every corner of the globe! π
Additional Exercises β
Let's test your skills with more challenges involving data manipulation and DOM interaction.
Exercise 5: Display Languages Spoken β
Goal: For each country, display the languages spoken.
Task β
- Access the
languages
property of each country. - Display the list of languages under each country's information.
Task
- Handle countries with multiple languages.
- Use
Object.values()
to extract language names.
Solution
function displayCountriesWithLanguages() {
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) => {
const countryCards = countries
.map((country) => {
const languages = country.languages
? Object.values(country.languages).join(", ")
: "No languages data";
return `
<div class="country-card">
<img src="${country.flags.svg}" alt="${country.name.common} flag">
<h2>${country.name.common}</h2>
<p><strong>Languages:</strong> ${languages}</p>
</div>
`;
})
.join("");
document.body.innerHTML = countryCards;
});
}
displayCountriesWithLanguages();
Exercise 6: Sorting Countries Alphabetically β
Goal: Allow users to sort the displayed countries alphabetically by name.
Task β
- Add a button or dropdown to select sorting order (A-Z or Z-A).
- Implement the sorting functionality.
Task
- Use
.sort()
on the countries array before displaying. - Update the display when the sorting order changes.
Solution
let countriesData = [];
let sortOrder = "asc";
function displayCountries() {
const sortedCountries = [...countriesData].sort((a, b) => {
if (a.name.common < b.name.common) return sortOrder === "asc" ? -1 : 1;
if (a.name.common > b.name.common) return sortOrder === "asc" ? 1 : -1;
return 0;
});
const countryCards = sortedCountries.map(/* same as before */).join("");
document.getElementById("countriesContainer").innerHTML = countryCards;
}
document.getElementById("sortButton").addEventListener("click", () => {
sortOrder = sortOrder === "asc" ? "desc" : "asc";
displayCountries();
});
function displayAllCountries() {
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) => {
countriesData = countries;
displayCountries();
});
}
displayAllCountries();
Add the sorting button:
<button id="sortButton">Toggle Sort Order</button>
<div id="countriesContainer"></div>
Exercise 7: Currency Converter β
Goal: For each country, display its currency and provide a simple currency converter to USD.
Task β
- Access the
currencies
property of each country. - Use an exchange rate API to get conversion rates.
- Implement a function to convert amounts.
Task
- Use Exchange Rate API or any other free service.
- Be mindful of API key requirements and rate limits.
Solution
Due to complexity and the need for an API key, here's a simplified version:
function displayCountriesWithCurrencies() {
fetch("https://restcountries.com/v3.1/all")
.then((res) => res.json())
.then((countries) => {
const countryCards = countries
.map((country) => {
const currency = country.currencies
? Object.values(country.currencies)[0]
: { name: "N/A", symbol: "" };
return `
<div class="country-card">
<img src="${country.flags.svg}" alt="${country.name.common} flag">
<h2>${country.name.common}</h2>
<p><strong>Currency:</strong> ${currency.name} (${currency.symbol})</p>
</div>
`;
})
.join("");
document.body.innerHTML = countryCards;
});
}
displayCountriesWithCurrencies();
Implementing the currency converter would require fetching exchange rates and adding input fields for user interaction.
Keep Exploring! β
With these exercises, you've expanded your skills in:
- Fetching and handling API data.
- Manipulating complex data structures.
- Enhancing user interfaces with interactivity.
- Handling asynchronous operations in JavaScript.
Remember, practice is key to mastery. Keep challenging yourself with new projects and ideas!
Happy coding, and may your JavaScript adventures be as limitless as the world itself! ππ