Kartik singh
Kartik singh

Reputation: 29

Input event listener not working at times

I have made a simple API based project wherein whenever the user enter some number in the input, the div below it with the output becomes visible and when the input field is empty the div gets hidden again as it was in the initial state. The problem is whenever I clear the input field gradually the setup works as expected but if I clear it quickly the div doesn't hide the div at all. Below given is the reference code for the same

let input = document.querySelector("#number-input");
      let fact = document.querySelector(".fact-content");
      input.addEventListener("input", getFact);
      function getFact() {
        let number = input.value;
        if (number != "") {
          let xhr = new XMLHttpRequest();
          xhr.open("GET", "http://numbersapi.com/" + number);
          xhr.onload = function () {
            if (this.status == 200) {
              fact.innerText = this.responseText;
              fact.style.display = "block";
            }
          };
          xhr.send();
        } 
        else{
          fact.innerText = "";
          fact.style.display = "none";
        }
      }
@import url('https://fonts.googleapis.com/css2?family=Varela+Round&display=swap');
*{
    margin: 0;
    padding: 0;
    font-family: 'Varela Round', sans-serif;
    box-sizing: border-box;
}
body{
    background-color: #9AD0EC;
}
main{
    width: 100%;
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
.container{
    width: 40%;
    margin: auto;
    line-height: 1.6;
    background-color: #1572A1;
    color: #eee;
    padding: 2rem;
    min-width: 500px;
    border-radius: 5px;
}
.container h1{
    font-size: 1.5em;
}
.container h4{
    font-size: 1.2rem;
}
input{
    padding: 0.5rem;
    border-radius: 5px;
    border: none;
    margin: 10px 0;
    width: 50%;
}
.fact-content{
    display: none;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>Number Fact</title>
  </head>
  <body>
    <main>
      <div class="container">
        <h1>Get random fact based on numbers</h1>
        <h4>Enter a number and get a random fact</h4>
          <input
            type="number"
            id="number-input"
            placeholder="Enter a number..."
          />
        <p class="fact-content"></p>
      </div>
    </main>
  </body>
</html>

Upvotes: 0

Views: 686

Answers (1)

Richard Deeming
Richard Deeming

Reputation: 31208

Consider the case when you have two characters in your input. You delete one character, and initiate an AJAX request for the remaining character. Before that AJAX request completes, you delete the remaining character.

When you delete the final character, the event handler clears and hides the element. But then the previous AJAX request completes, and displays the outdated response in the element.

There are two things you can do here:

  1. When the AJAX request completes, check that the input value is still the same as the number variable. If it's not, discard the response to the AJAX request.

  2. Switch to using the fetch API, and use an AbortController instance to abort the in-flight request when the input value changes.

let input = document.querySelector("#number-input");
let fact = document.querySelector(".fact-content");
let abortToken = null;

input.addEventListener("input", getFact);

async function getFact() {
  if (abortToken) {
    abortToken.abort("Input changed");
    abortToken = null;
  }

  let number = input.value;
  if (!number) {
    fact.innerText = "";
    fact.style.display = "none";
    return;
  }
  
  const url = `http://numbersapi.com/${number}`;
  abortToken = new AbortController();
  const { signal } = abortToken;
  try {
    const response = await fetch(url, { signal });
    if (input.value !== number) {
        // The input has been modified.
        return;
    }
    
    if (!response.ok){
        const errorMessage = await response.text();
        console.error(response.status, response.statusText, errorMessage);
        fact.innerText = "# ERROR #";
        fact.style.display = "block";
        return;
    }
    
    const text = await response.text();
    fact.innerText = text;
    fact.style.display = "block";
  } catch {
  }
}
@import url('https://fonts.googleapis.com/css2?family=Varela+Round&display=swap');
*{
    margin: 0;
    padding: 0;
    font-family: 'Varela Round', sans-serif;
    box-sizing: border-box;
}
body{
    background-color: #9AD0EC;
}
main{
    width: 100%;
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
.container{
    width: 40%;
    margin: auto;
    line-height: 1.6;
    background-color: #1572A1;
    color: #eee;
    padding: 2rem;
    min-width: 500px;
    border-radius: 5px;
}
.container h1{
    font-size: 1.5em;
}
.container h4{
    font-size: 1.2rem;
}
input{
    padding: 0.5rem;
    border-radius: 5px;
    border: none;
    margin: 10px 0;
    width: 50%;
}
.fact-content{
    display: none;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>Number Fact</title>
  </head>
  <body>
    <main>
      <div class="container">
        <h1>Get random fact based on numbers</h1>
        <h4>Enter a number and get a random fact</h4>
          <input
            type="number"
            id="number-input"
            placeholder="Enter a number..."
          />
        <p class="fact-content"></p>
      </div>
    </main>
  </body>
</html>

Upvotes: 1

Related Questions