What causes this failure to update an array on keyup

Viewed 246

I am working at displaying a JSON in the form of an HTML table using plain (vanilla) JavaScript. There is a search (filter) functionality too:

class CountriesList {
  constructor() {
    this.apiURL = "https://gist.githubusercontent.com/Goles/3196253/raw/9ca4e7e62ea5ad935bb3580dc0a07d9df033b451/CountryCodes.json";
    this.countries = [];
    this.searchBox = document.querySelector('#searchBox');
    this.stringToMatch = '';
    this.tableRows = '';
  }

  // Get Items
  async getFilteredCountries() {
    const response = await fetch(this.apiURL);
    this.countries = await response.json();
    // If there is a search string, filter results
    this.stringToMatch = this.searchBox.value;
    if (this.stringToMatch.length > 0) {
      this.countries = this.countries.filter(country => {
        return country.name.toLowerCase().includes(this.stringToMatch.toLowerCase()) || country.code.includes(this.stringToMatch.toUpperCase());
      });
    }
  }

  // Render rows
  renderRows(arr, container) {
    let el = document.querySelector(container);
    el.innerHTML += arr.map(function(item) {
      return `<tr>
          <td>${item.name}</td>
          <td class="text-right">${item.code}</td>
       </tr>`
    }).join('');
  }

  async hideLoader() {
    let loader = document.querySelector('.loader');
    const action = this.countries.length > 0 ? 'add' : 'remove';
    loader.classList[action]('d-none');
  }

  async init() {
    await this.getFilteredCountries();
    await this.hideLoader();
    this.searchBox.addEventListener("keyup", this.getFilteredCountries());
    this.renderRows(this.countries, '#countries_table tbody');
  }
}

const countriesList = new CountriesList();
countriesList.init();
.box {
  position: relative;
  min-height: 90vh;
}

.loader {
  border: 4px solid #ccc;
  border-top-color: transparent;
  border-radius: 50%;
  width: 50px;
  height: 50px;
  position: absolute;
  top: 110px;
  left: 50%;
  margin-left: -50px;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet"/>
<div class="container-fluid">
  <div class="box">
    <div class="search">
      <input class="my-2 form-control" id="searchBox" type="text" placeholder="Search..." value="">
    </div>
    <table class="table" id="countries_table">
      <thead>
        <tr>
          <th>Country</th>
          <th class="text-right">Code</th>
        </tr>
      </thead>
      <tbody>
      </tbody>
    </table>
    <div class="loader"></div>
  </div>
</div>

The problem

The script fails to update the countries array (and render the table again) on keyup.

What am I doing wrong?

3 Answers

There were some reasons for which your renderRows method was not being invoked. The first thing is while adding a method as an event listener you should write the function name like this this.getFilteredCountries instead of this.getFilteredCountries(). I separated the API call in a different method because it should be done only once and while filtering we can filter the array. The main thing is that while using Class we need to bind methods or use ES6 syntax otherwise all the class properties will be undefined refer to this. The rest part is simple. I wrote a separate method updateRows() which will clear the innerHTML at first and show the result. Hope this solves your problem.

class CountriesList {
  constructor() {
    this.apiURL =
      "https://gist.githubusercontent.com/Goles/3196253/raw/9ca4e7e62ea5ad935bb3580dc0a07d9df033b451/CountryCodes.json";
    this.countries = [];
    this.searchBox = document.querySelector("#searchBox");
    this.stringToMatch = "";
    this.tableRows = "";
  }

  // Get Items
  getFilteredCountries = async () => {
    const response = await fetch(this.apiURL);
    this.countries = await response.json();
    // If there is a search string, filter results
    this.stringToMatch = this.searchBox.value;
    if (this.stringToMatch.length > 0) {
      this.countries = this.countries.filter((country) => {
        return (
          country.name
            .toLowerCase()
            .includes(this.stringToMatch.toLowerCase()) ||
          country.code.includes(this.stringToMatch.toUpperCase())
        );
      });
      this.renderRows(this.countries, "#countries_table tbody");
    }
  };

  // Render rows
  renderRows = (arr, container) => {
    let el = document.querySelector(container);
    el.innerHTML = "";
    el.innerHTML += arr
      .map(function (item) {
        return `<tr>
              <td>${item.name}</td>
              <td class="text-right">${item.code}</td>
           </tr>`;
      })
      .join("");
  };

  hideLoader = async () => {
    let loader = document.querySelector(".loader");
    const action = this.countries.length > 0 ? "add" : "remove";
    loader.classList[action]("d-none");
  };

  init = async () => {
    await this.getFilteredCountries();
    await this.hideLoader();
    this.searchBox.addEventListener("keyup", this.getFilteredCountries);
    this.renderRows(this.countries, "#countries_table tbody");
  };
}

const countriesList = new CountriesList();
countriesList.init();
.box {
  position: relative;
  min-height: 90vh;
}

.loader {
  border: 4px solid #ccc;
  border-top-color: transparent;
  border-radius: 50%;
  width: 50px;
  height: 50px;
  position: absolute;
  top: 110px;
  left: 50%;
  margin-left: -50px;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
<link
  href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
  rel="stylesheet"
/>
<link rel="stylesheet" href="./style.css" />
<div class="container-fluid">
  <div class="box">
    <div class="search">
      <input
        class="my-2 form-control"
        id="searchBox"
        type="text"
        placeholder="Search..."
        value=""
      />
    </div>
    <table class="table" id="countries_table">
      <thead>
        <tr>
          <th>Country</th>
          <th class="text-right">Code</th>
        </tr>
      </thead>
      <tbody></tbody>
    </table>
    <div class="loader"></div>
  </div>
</div>
<script src="./script.js"></script>

You may need to recall your renderRows function after getFilteredCountries on keyup.

You need to call renderRows function after the filter of values is over (You can call it inside the getFilteredCountries function itself).

I have updated the code and placed a screenshot of that. Please check whether it helps. enter image description here

Related