Unable to get data from API in JS

hey guys this is my small js api project. In it i am asking user to randomly select their country and a year and showing them all the holidays of their country. If they select a holiday I am showing them how much time is left to that particular day from that day when user selected it

this uses two API HOLIDAY API, COUNTRY API(API to get iso code for selected country)
to get all the countries , their holidays and their ISO-3166 alphaCode 2 (to pass as id in the apiHolidayCall(id , year).
My problem is that first api is asking for country in the form of id and year
as parameter. I am giving user all the countries option to select from and a year.
country and year received from here is passed to apiHolidayCall(id, year) function but the thing is country is passing to the function but i’m not able to pass year value to that function using yearsInt.innerText.
Please help me with this issue and suggest me how can i refactor my code

When I console.log(id, year) in your apiHolidayCall function, I get undefined 2020

(had to setup a timeout to run your execution function again after I had entered the country and year, because nothing seems to be triggering it with your submit button not doing anything)

hey can you elaborate what you did

console.log(id, year) is undefined because initially when the DOM is loaded it doesn’t get any country and year selected because of execution(). I can’t add another form just to get those and then show options i want effortless display of holidays once country and year is entered

Sure, here I’m logging id and year:

const apiHolidayCall = (id , year) => {
  console.log('id,year: ',id,year)
  ....
}

The timeout (I’ve made it an interval), right after your execution function runs the first time:

execution();

setInterval(()=>{
  execution();
},5000)

When execution() runs for the first time, the user hasn’t entered anything yet.

const execution = (countryId) => {
  //first display all the countries options
  apiCountryCall();

  //get all the selected values
  let selected_countryValue = selected_country.value; //selected Country Value
  let selectedyearValue = yearsInt.value; //entered year Value

  //if all the values are valid pass these values in the apiHolidayCall() to show all the holidays associated with that country
  if(selected_countryValue !="" && selectedyearValue !=""){
    apiHolidayCall(countryId, selectedyearValue);
    console.log(selectedyearValue);
    console.log(selectedyearValue);
  }
}

So obviously, your if block which checks if neither selected_countryValue nor selectedyearValue are empty strings won’t execute. Unless you force it by an interval (or just wire up that submit button).

There’s no need for a second form, you can just put the execution function into your button event listener.

form.addEventListener('submit', e => {
  e.preventDefault();
  execution()
})

You’ll see that the year you’ve entered is there, the country is still undefined. I haven’t looked too closely where that id is supposed to be coming from, so you might have to add more functions to your button event.

1 Like

that was clever. So you are suggesting that I should call execution() again after 5s giving user time to fill year value and then show apiHolidayCall()and pass year.

hey but don’t you think this is not the exact way of doing such a thing like what if user entered year before 5s .

Screw the timeout :smiley:

Just trigger it with your button (see above)

i’m sorry which button.

In my post above. The conversation was a bit hectic, you might have missed it.

form.addEventListener('submit', e => {
  e.preventDefault();
  execution()
})

You’ll see that the year you’ve entered is there, the country is still undefined. I haven’t looked too closely where that id is supposed to be coming from, so you might have to add more functions to your button event.

no but my form works only when I submit all the details. You know like when if according to you when a user will type year and hit enter my form event listener will come into picture and then it’ll show the holiday options. But how would this same form event capture event when every detail (country, year, holiday) selected would submit

But your problem is that the execution function is being called once, hence you’ll never actually get the holidays.

What you should do:

  1. Fill the countries. This is done, but you’re missing something: you should add the iso-3166 that to every option so you have a reference later (this should make a call to filterId unnecessary). Like this:
arr.forEach(({ uuid, country_name, "iso-3166": iso3166 }) => {
    let div = document.createElement("div");
    div.className = "option";
    // UUIDs should be unique,
    // so use it for the input id :)
    div.innerHTML = `
    <label class="label-text" for="${uuid}">${country_name}</label>
    <input type="radio" class="radio" id="${uuid}" value="${iso3166}" name="category">
    `;
});
  1. Once the user selects the country (when they click on any option), set a data attribute to the selected_country div: selected_country.dataset.iso = selected_input.value. Basically, here you would be reading the value attribute of the selected <input type="radio" class="radio" />.

  2. Add a way to detect when the user has typed a valid year and there’s a selected country too:

// You could attach this to: years.keyup event:
// years.keyup = (e) => { }

if (years.value >= 1970 && selected_country.dataset.iso) {
  // Here call the apiHolidayCall with the 'iso' and year
}

All of this should be setup once on the execution function; no need to re setup everything multiple times :slight_smile:.

One more thing, since the calls to the API are limited, you should be caching the queries. For instance, the countries API call should rarely be updated, so you could store it on the localStorage:

// Inside your apiCountryCall
const storedData = localStorage.getItem('countries')
if (storedData) {
  displayCountriesOptions(JSON.parse(storedData));
  return;
}
fetch(url).then(result => result.json).then(data => {
  localStorage.setItem('countries', JSON.stringify(data.response.countries));
  displayCountriesOptions(data.response.countries);
});

hey @skyparate, first of all, thank you for reviewing my code. I am trying to go through your steps one by one and trying to understand them.

  1. As for your first suggestion, I did change my code
const displayCountriesOptions = arr => {
  arr.forEach(({ uuid, country_name, "iso-3166": alpha2Code }) => {
    let div = document.createElement('div');
    div.className = "option";
    div.innerHTML = `
    <label class="label-text"  for="${uuid}">${country_name}</label>
    <input type="radio" class="radio" id="${uuid}" value="${iso3166}" name="category">
    `;
    options_country.appendChild(div);
  })

  //add selected country to selected box
  options_country.addEventListener('click', e => {
    options_cont.innerHTML = "";
    // console.log(e.target);
    if (e.target.classList.contains('label-text')) {
      selected_country.innerText = e.target.innerText;
      let selected_input = selected_country.querySelector('.category');
      // console.log(selected_country.innerText);
      console.log(selected_input.value)
      selected_country.dataset.iso = selected_input.value;
    }

    options_country.classList.remove('active');
  })

}

firstly sorry but I didn’t understand how we passed those parameters to the displayCountriesOptions() forEach loop { uuid, country_name, "iso-3166": iso3166 } .
this gave error ReferenceError: iso3166 is not defined this is maybe because in apiCountryCall() i pass the array response of the API (data.response.countries)

for 2 suggestion
i did

  options_country.addEventListener('click', e => {
    options_cont.innerHTML = "";
    // console.log(e.target);
    if (e.target.classList.contains('label-text')) {
      selected_country.innerText = e.target.innerText;
      let selected_input = selected_country.querySelector('.category');
      // console.log(selected_country.innerText);
      console.log(selected_input.value)
      selected_country.dataset.iso = selected_input.value;
    }

but was not able to select any country. I know I’m being fool and you’ve been so generous to explain each point to but can you tell me once again what you actually meant. This is because I have never used data-attribute and dataset.iso properties in js.
Hey, you said to use UUID as id for input: radio how can get that.

I am sorry for being such foolish and thank you for your patience. :slightly_smiling_face:

I know i am asking for too much but it would be kind enough if you can correct my code in codepen.
:slightly_smiling_face:

firstly sorry but I didn’t understand how we passed those parameters to the displayCountriesOptions() forEach loop

Here:

arr.forEach(({ uuid, country_name, "iso-3166": alpha2Code })

The forEach callback function receives, as the first parameter, the element being iterated, right? The previous code is exactly the same as this:

const uuid = el.uuid,
  country_name = el.country_name,
  alpha2Code = el['iso-3166'];

This expression is called object destructuring, and is just a shorter way to assign variables. It works with arrays too: const [a, b, c] = [1, 2, 3] // a = 1, b = 2, c = 3.

Read more about destructuring here.

this gave error ReferenceError: iso3166 is not defined

That’s because you changed the variable name here: arr.forEach(({ uuid, country_name, "iso-3166": alpha2Code })

Even if you rename it as iso3166 the code won’t work correctly. The idea is fine, but here:

    if (e.target.classList.contains('label-text')) {
      selected_country.innerText = e.target.innerText;
      let selected_input = selected_country.querySelector('.category');
      // console.log(selected_country.innerText);
      console.log(selected_input.value)
      selected_country.dataset.iso = selected_input.value;
    }

The problem is that selected_country.querySelector('.category'); will return the first element with a class of category, but since there are as many as countries are, then it will not be correct one (unless the user selects the first element on the list).

To fix it, you need to attach the listener to each div.option instead:

let div = document.createElement("div");
    div.className = "option";
    // SKAP: Add the value of the iso code (required by the API)
    div.innerHTML = `
    <label class="label-text" for="${uuid}">${country_name}</label>
    <input type="radio" class="radio" id="${uuid}" value="${iso3166}" name="category">
    `;
    options_country.appendChild(div);
    div.addEventListener("click", (e) => {
        // Here you handle each element click
        options_cont.innerHTML = "";
        const { currentTarget: target } = e;
        const input = target.querySelector("input"),
                label = target.querySelector("label");
        // Handle the rest of the logic
    });

I don’t mind fixing your code, but my intention is to help you learn instead of just giving you the answer :slight_smile:.