Vue.js powered search bar for exploring countries worldwide (utilizing the restcountries API)

Despite successfully loading the API country data (as evidenced by the console.log() entry and the accompanying picture of my work desk), the country information does not display when hovering the mouse cursor over the search bar field (expecting a dropdown of all countries) or typing in a country name to filter the list. What am I missing in this scenario?

My intention was to present the selected country data (capital, currency, language, timezone, etc.) from the API in the designated box, with the country flag serving as the background image!

// A function to format numbers with suffix letters
function formatNumber(labelValue) {
  return Math.abs(Number(labelValue)) >= 1.0e+9
    ? Math.abs(Number(labelValue)) / 1.0e+9 + "B"
  : Math.abs(Number(labelValue)) >= 1.0e+6
    ? Math.abs(Number(labelValue)) / 1.0e+6 + "M"
  : Math.abs(Number(labelValue)) >= 1.0e+3
    ? Math.abs(Number(labelValue)) / 1.0e+3 + "K"
  : Math.abs(Number(labelValue));
}

const settings = {
    async: true,
    crossDomain: true,
    url: `https://restcountries.com/v3.1/all`,
    method: 'GET',
    headers: {
        'X-RapidAPI-Key': 'b56451a2dcmsh7ba7285037af267p1435dajsn71561b599c58',
        'X-RapidAPI-Host': 'rest-countries10.p.rapidapi.com'
    }
};

$.ajax(settings).done(function (response) {
    console.log(response);
});

var vm = Vue.createApp({
  el: '#main',

  data: {
    search: '',
    selectedName: '',
    selectedCode: '',
    selectedCapital: '',
    selectedLanguage: '',
    selectedCurrency: '',
    selectedLatlng: '',
    selectedLatlngUrl: '',
    selectedPopulation: '',
    selectedArea: '',
    selectedSubregion: '',
    selectedGini: '',
    selectedTimezone: '',
    selectedFlagSrc: '',
  },

  computed: {
    countries () {
      var self = this;
      var countries;

      // Fetch JSON data from API
      $.ajax({
        async: false,
        url: `https://restcountries.com/v3.1/all`,
        success: function(data){
          countries = data;
        } 
      });
        
      // Filter the countries by name in all languages
      countries = countries.filter(function(value, index, array) {
        return value['name'].toLowerCase().includes(self.search.toLowerCase())
        || Object.values(value['translations']).join(' ').toLowerCase().includes(self.search.toLowerCase());
      });
      return countries; 
    },
  },

  // Automatically select the first country in the list upon loading
  beforeMount() {
    var self = this;
    var found = false;
    this.countries.forEach(function(element) {
      if(element['alpha2Code'] === 'US') {
        self.selectCountry(element);
        found = true;
      }
    });      

    if(!found)
        this.selectCountry(this.countries[0]);

  },        

  methods: {
    // Return the country name
    getName (country) {
      return (country['name']);
    },

    // Return the URL of the country flag
    getFlagSrc (country) {
      return (country['flag'] || 'N/A');
    },

    // Set the selected country data
    selectCountry (country) {
      var self = this;

      $('section').animate({
        opacity: 0
      }, 150, function() {

        self.selectedName = (country['name'] || 'N/A');
        self.selectedFlagSrc = self.getFlagSrc(country);
        self.selectedCode = (country['alpha2Code'] || 'N/A') + ' / ' + (country['alpha3Code'] || 'N/A');
        self.selectedCapital = (country['capital'] || 'N/A');

        var arrayLanguage = [];
        country['languages'].forEach(function(element) {
          arrayLanguage.push(element['name']);
        });
        self.selectedLanguage = (country['languages'].length > 0) ? arrayLanguage.join(', ') : 'N/A';

        var arrayCurrency = [];
        country['currencies'].forEach(function(element) {
          arrayCurrency.push(element['name'] + ' ' + element['symbol']);
        });
        self.selectedCurrency = (country['currencies'].length > 0) ? arrayCurrency.join(', ') : 'N/A';

        self.selectedLatlng = (country['Latlng'].length > 0) ? ('Lat: ' + country['Latlng'][0] + ', Lng: ' + country['Latlng'][1]) : 'N/A';
        self.selectedLatlngUrl = (country['Latlng'].length > 0) ? ('https://www.google.com/maps/?q=' + country['Latlng'][0] + ',' + country['Latlng'][1]) : '';
        self.selectedPopulation = country['Population'] ? formatNumber(country['Population']) : 'N/A';
        self.selectedArea = country['Area'] ? (formatNumber(country['Area']) + ' km²') : 'N/A';
        self.selectedSubregion = (country['Subregion'] || 'N/A');
        self.selectedGini = country['Gini'] ? (country['Gini'] + '%') : 'N/A';
        self.selectedTimezone = (country['Timezones'].length > 0) ? country['Timezones'].join(', ') : 'N/A';

        $('section').animate({
          opacity: 1
        });
      });
    },
  }

});

Answer №1

It's generally not recommended to make asynchronous calls in a computed property. Each time the user types and updates the search variable, the computed property is called resulting in numerous requests per user. Instead, you should make that call once and populate a variable with the results in a lifecycle hook like created, mounted, etc:

data() {
    return {
      countries: [],
    };
  },
  mounted() {
    (async () => {
      const response = await fetch("https://restcountries.com/v3.1/all");
      this.countries = await response.json();
    })();
  },

Once you have your list of countries, you can filter them in your computed property:

data() {
    return {
      countries: [],
      search: "",
    };
  },
  mounted() {
    (async () => {
      const response = await fetch("https://restcountries.com/v3.1/all");
      this.countries = await response.json();
    })();
  },
  computed: {
    filteredCountries() {
      if (this.countries.length === 0) return [];
      return this.countries.filter((country) => {
        return country.name.common
          .toLowerCase()
          .includes(this.search.toLowerCase());
      });
    },
  },

Now it's up to you to create a logic for how the user selects a filtered country. It could involve clicking on the list and displaying the selected country. As an additional example, I've created another computed property that returns the first country in the filtered list, along with a click listener in the list that populates the search field.

data() {
    return {
      countries: [],
      search: "",
    };
  },
  mounted() {
    (async () => {
      const response = await fetch("https://restcountries.com/v3.1/all");
      this.countries = await response.json();
    })();
  },
  computed: {
    filteredCountries() {
      if (this.countries.length === 0) return [];
      return this.countries.filter((country) => {
        return country.name.common
          .toLowerCase()
          .includes(this.search.toLowerCase());
      });
    },
    selectedCountry() {
      return this.filteredCountries[0];
    },
  },
  methods: {
    selectCountry(index) {
      this.search = this.filteredCountries[index].name.common;
    },
  },

For a working example, check out: https://jsfiddle.net/andresabadia/n0acsj5x/5/

const {
  createApp
} = Vue

createApp({
  data() {
    return {
      countries: [],
      search: "",
    };
  },
  mounted() {
    (async() => {
      const response = await fetch("https://restcountries.com/v3.1/all");
      this.countries = await response.json();
    })();
  },
  computed: {
    filteredCountries() {
      if (this.countries.length === 0) return [];
      return this.countries.filter((country) => {
        return country.name.common
          .toLowerCase()
          .includes(this.search.toLowerCase());
      });
    },
    selectedCountry() {
      return this.filteredCountries[0];
    },
  },
  methods: {
    selectCountry(index) {
      this.search = this.filteredCountries[index].name.common;
    },
  },
}).mount('#app')
li {
  cursor: pointer
}
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<div id="app">
  <label for="country">country search</label>
  <input id="country" type="text" v-model="search" />
  <ul>
    <li v-for="(country, index) in filteredCountries" @click="selectCountry(index)">
      {{ country.name.common }}
    </li>
  </ul>
  <div v-if="selectedCountry">
    <h2>{{ selectedCountry.name.common }}</h2>
    <img :src="selectedCountry.flags.png" alt="" />
    <p v-for="capital in selectedCountry.capital">{{ capital }}</p>
  </div>
</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What sets apart using JQuery to run AJAX from using plain XMLHttpRequest for AJAX requests?

Can you explain the advantages and disadvantages of using JQuery versus XMLHttpRequest for running AJAX code? While I understand that JQuery is essentially a JavaScript library, there must be some key differences to consider. Please elaborate on this top ...

Using AngularJS $http.jsonp() method to interface with Google Maps Distance Matrix API

I am currently working on integrating the Google Maps Distance Matrix API into my project to calculate distances between two points using specific coordinates. My implementation involves AngularJS and the $http.jsonp() method to make requests to the API: ...

Uploading profile images with AngularJS and Firebase

I am encountering an issue with my JavaScript image uploader. It successfully uploads an image and provides the image src, but I need to save this value in my FireBase DB for the current user that is being created. Initially, I attempted to output the img ...

How can I use JavaScript api calls to retrieve an image url and insert it into an image tag in an

I have a JSON object that I need to use to retrieve images from a remote URL and display them in the img tag using API calls. The API link can be found at <div class="emoji"> <ul id="emojiz"></ul> <span style= ...

Sending PHP variables to a pie chart in JavaScript via GoogleSome options for passing

I am currently working on a project where I have a 3D pie chart from Google. I am trying to pass PHP variables through to represent the percentages on the chart, which are pulled from a database. However, I am running into an issue where the percentage dis ...

What is the best way to handle multiple responses in Ajax within a single function?

Here is a simple code snippet: $.ajax({ url:'action.php', method: 'POST', data:{getcart:1}, success:function(response){ $('#getcart').html(response);//want to ...

The checkbox is not updating as anticipated

I'm currently developing a basic widget that allows the user to change the value of either the check box OR the entire div by selecting them. Below is the code I have implemented: $(document).ready(function() { $(document).on("click", ".inputChec ...

Differences between an AngularJS function() and a factory function() when used in a

When it comes to Angular, I've come across directives being written in two different ways: .directive('example', function () { // Implementation }); .directive('example', function factory() { // Implementation }) Can you ...

Access your Angular 5 application as a static webpage directly in your browser, no server required!

I need to run an Angular 5 application in a browser by manually opening the index.html file without the use of a server. My client does not have access to any servers and simply wants me to provide a package (dist folder) for them to double-click on the in ...

Unable to utilize the useState hook in TypeScript (Error: 'useState' is not recognized)

Can you identify the issue with the following code? I am receiving a warning from TypeScript when using useState import * as React, { useState } from 'react' const useForm = (callback: any | undefined) => { const [inputs, setInputs] = useS ...

Error encountered: Unexpected '<' token when trying to deploy

Trying to deploy a React app with React Router on a Node/Express server to Heroku, but encountering the following error... 'Uncaught SyntaxError: Unexpected token <' Suspecting the issue may lie in the 'catch all' route in the Expr ...

The Vue.js component was unable to retrieve the data

I am facing an issue with accessing data inside a simple component. Here is the code for my component: <template> <!-- success --> <div class="message-box message-box-success animated fadeIn" id="message-box-success"> <div cl ...

Error: FileReader is not defined in Node.js (Nest.js) environment

I am working on converting my image to base64 using a custom function. However, when I try to execute the code, I encounter an error message stating ReferenceError: FileReader is not defined. This error has left me puzzled and unsure of its cause. Below i ...

Having trouble with jQuery focus not functioning properly?

I've been attempting to implement a focus function on a specific input in order to display a div with the class name .search_by_name when focused. However, I'm encountering issues and would appreciate it if someone could review my code to identif ...

Running a shared function following every Promise resolution

Currently working on a nodejs project and utilizing bluebird.js for Promises. I'm looking to have a function executed after each .then() method in the chain. Does anyone know of a way or API within bluebird.js that supports this? Appreciate any guida ...

I'm experiencing an issue with Bootstrap 5 where the code runs fine on codeply but not on my local machine

I'm attempting to replicate the scrollspy example found on the Bootstrap website. You can see my attempt here: . Feel free to inspect the code. While the navigation links function correctly, I've noticed that according to Bootstrap's docum ...

What is the best way to integrate a backend with the webpack template?

Recently diving into Vue.js and Webpack, I have decided to utilize the webpack template provided by vue-cli for my new project. Now that I have generated this project, I am interested in incorporating a backend system. I'm wondering if it would be wi ...

Enable strict mode for older web browsers

I would like to incorporate the "use strict"; statement into my function, but unfortunately it is not compatible with older browsers such as ie7 and ie8. Is there a workaround to ensure this functionality works in legacy browsers? Could someone please cla ...

A Guide to Sorting Nested Lists with SortableJS and jQuery

I have been experimenting with SortableJS and jQuery SortableJS Binding in order to create a nested list, capture the new order of the list and its children (resembling a hierarchical structure) using console.log(). I attempted the solution provided in th ...

Comparing non-blocking setTimeout in JavaScript versus sleep in Ruby

One interesting aspect of JavaScript is that, being event-driven in nature, the setTimeout function does not block. Consider the following example: setTimeout(function(){ console.log('sleeping'); }, 10); console.log('prints first!!') ...