0

I'm having trouble to return a few keys from objects which are coming from promises callback function. The error I'm getting is on second country:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'png')

'use strict';

const btn = document.querySelector('.btn-country');
const countriesContainer = document.querySelector('.countries');
const renderCountry = function (data, className = '') {
  const html = `
  <article class="country ${className}">  
  <img class="country__img" src="${data.flags.png}" />
    <div class="country__data">
      <h3 class="country__name">${data.name.official}</h3>
      <h4 class="country__region">${data.region}</h4>
      <p class="country__row"><span></span> ${(
        +data.population / 1000000
      ).toFixed(1)} millions people</p>
      <p class="country__row"><span>️</span>${Object.values(
        data.languages
      )}</p>
      <p class="country__row"><span></span>${Object.keys(data.currencies)}</p>
</article>`;
  countriesContainer.insertAdjacentHTML('beforeEnd', html);
  countriesContainer.style.opacity = 1;
};
const getCountryData = function (country) {
  // Country 1
  fetch(`https://restcountries.com/v3.1/name/${country}`)
    .then(response => response.json())
    .then(data => {
      renderCountry(data[0]);
      console.log(data);
      const neighbour = data[0].borders[0];
      if (!neighbour) return;
      // Country 2
      return fetch(`https://restcountries.com/v3.1/alpha/${neighbour}`);
    })
    .then(responce => responce.json())
    .then(data => renderCountry(data, 'neighbour'));
};

getCountryData('portugal');

Here index if needed just that you can recreate:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="stylesheet" href="style.css" />
    <script defer src="Asynchronous.js"></script>
    <title>Asynchronous JavaScript</title>
  </head>
  <body>
    <main class="container">
      <div class="countries"></div>
      <!-- <bn class="btn-country">Where am I?</button> -->
      <div class="images"></div>
    </main>
  </body>
</html>

And CSS

* {
  margin: 0;
  padding: 0;
  box-sizing: inherit;
}

html {
  font-size: 62.5%;
  box-sizing: border-box;
}

body {
  font-family: system-ui;
  color: #555;
  background-color: #f7f7f7;
  min-height: 100vh;

  display: flex;
  align-items: center;
  justify-content: center;
}

.container {
  display: flex;
  flex-flow: column;
  align-items: center;
}

.countries {
  /* margin-bottom: 8rem; */
  display: flex;

  font-size: 2rem;
  opacity: 0;
  transition: opacity 1s;
}

.country {
  background-color: #fff;
  box-shadow: 0 2rem 5rem 1rem rgba(0, 0, 0, 0.1);
  font-size: 1.8rem;
  width: 30rem;
  border-radius: 0.7rem;
  margin: 0 3rem;
  /* overflow: hidden; */
}

.neighbour::before {
  content: 'Neighbour country';
  width: 100%;
  position: absolute;
  top: -4rem;

  text-align: center;
  font-size: 1.8rem;
  font-weight: 600;
  text-transform: uppercase;
  color: #888;
}

.neighbour {
  transform: scale(0.8) translateY(1rem);
  margin-left: 0;
}

.country__img {
  width: 30rem;
  height: 17rem;
  object-fit: cover;
  background-color: #eee;
  border-top-left-radius: 0.7rem;
  border-top-right-radius: 0.7rem;
}

.country__data {
  padding: 2.5rem 3.75rem 3rem 3.75rem;
}

.country__name {
  font-size: 2.7rem;
  margin-bottom: 0.7rem;
}

.country__region {
  font-size: 1.4rem;
  margin-bottom: 2.5rem;
  text-transform: uppercase;
  color: #888;
}

.country__row:not(:last-child) {
  margin-bottom: 1rem;
}

.country__row span {
  display: inline-block;
  margin-right: 2rem;
  font-size: 2.4rem;
}

.btn-country {
  border: none;
  font-size: 2rem;
  padding: 2rem 5rem;
  border-radius: 0.7rem;
  color: white;
  background-color: orangered;
  cursor: pointer;
}

.images {
  display: flex;
}

.images img {
  display: block;
  width: 80rem;
  margin: 4rem;
}

.images img.parallel {
  width: 40rem;
  margin: 2rem;
  border: 3rem solid white;
  box-shadow: 0 2rem 5rem 1rem rgba(0, 0, 0, 0.1);
}

I tried methods which obviously don't work, so maybe you can spot and error and point it out!

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Roele
  • 1
  • 1
  • `if (!neighbour) return;` won’t stop the following `then`s from trying to render undefined neighbor. You should only render the neighbor if `data` contains something – James Nov 23 '22 at 21:34
  • The error message mentions the original error, and that means that your `data.flags` is undefined. Check your data source. – trincot Nov 23 '22 at 22:15
  • @James if (!neighbour) return; in my case not preventing function from working, but rather preventing an error where is no neighbour, if country is an island for example. – Roele Nov 24 '22 at 09:54
  • @trincot the problem is the same obect, with different data in it, so structure is the same : [{ojb}] -> "0"(Name of the first under group) and in it there two keys -> "png" and "svg", each of them contains link for the flag to render, mb i cannot extract it right, but this is the problem i dont know wich command should i use? – Roele Nov 24 '22 at 09:55
  • I don't understand what you want to say here. Anyway, the error means there is no `flags` property in one of the objects you process. Debugging can easily clarify which object it is. – trincot Nov 24 '22 at 10:08
  • @trincot im using 'restcountries' api, wich is returning same structure of keys for different countries, and so if no flags, i shouldt get the first call working, but i do. i bascily get the rendering of the first country : const getCountryData = function (country) { // Country 1 fetch(`https://restcountries.com/v3.1/name/${country}`) .then(response => response.json()) .then(data => { renderCountry(data[0]); console.log(data); wich is this, but dont get its for second one, but they have the same object to select from, this is my problem – Roele Nov 24 '22 at 10:15
  • Sorry, I don't understand what you are saying. You seem to expect that all objects will have the same structure. How can you be sure of that? Can you edit your question and clarify? Did you debug, and inspect the variables when putting break points, or at least print the object structure you actually get? That's the first thing you should do. – trincot Nov 24 '22 at 10:16
  • @trincot sorry im figthing with grammar here, basicly what i did, first of all i debug it to the point where i see, that im getting the same object right, but with different info in it, so its basicly like account 1 for parris and account 2 for portugal. but they have the same key; Object: { name of the country: population: flags: png(link to the flag of the current countrie_ in png format or svg ( in svg format).} this is why im sure i have the same structure, and also bring up all the code, so people may recreate and saw mistake mb. In case of debug im not sure, how i can fix it ;c – Roele Nov 24 '22 at 10:32
  • It is simple. When the object has the `flags` property you don't get the error. When the object does not have the `flags` property you do get the error. All the rest you are saying cannot be that relevant. Just **check** that the `flags` property exists in the object -- while debugging. – trincot Nov 24 '22 at 10:41
  • @trincot im sorry, i might be a complete mess with my answers but once again, if i move mouse over on the data.flags.png In debbuger console, on the first check before function return second time , im getting a link : https://flagcdn.com/w320/pt.png But while doing this for second time: im not getting a link and instead getting undefined and dont know why, or how i can spot an error, so i should get: https://flagcdn.com/w320/es.png – Roele Nov 24 '22 at 10:56

1 Answers1

0

The original error says:

TypeError: Cannot read properties of undefined (reading 'png')

This means that you are reading .png on something that is undefined. It occurs in this line;

<img class="country__img" src="${data.flags.png}" />

And it means that data.flags is undefined. This situation can be easily debugged, by inspecting what data is at the moment the error occurs with a debugger, or if you don't know how to use one, by outputting the variable in JSON format at the place where you want to inspect a variable:

const renderCountry = function (data, className = '') {
  console.log(JSON.stringify(data,null,2)); // <-- debugging code
  // ...etc

This outputs a JSON string that starts with [, and so that means that data is an array, and that explains why you get the error.

The code that calls the function above and passes this array is here:

.then(data => renderCountry(data, 'neighbour'));

Earlier in your code you have correctly treated data as an array, but then in your second retrieval (for Spain), you didn't. So correct the above line to:

.then(data => renderCountry(data[0], 'neighbour'));

I hope you now see how to debug such situations.

trincot
  • 317,000
  • 35
  • 244
  • 286