JavaScript issue on using Array.includes() and test duplicate items

Viewed 89

I have an input that uses an API to fetch some cities based on the letters inserted. The API is launched on each keyup event like so :

let ville_input = document.getElementById("search_immobilier_ville");
let ville_arr = [];

ville_input.addEventListener("keyup", () => {
  res_list.innerHTML = "";

  fetch("https://api.fr/communes?nom=" + ville_input.value)
    .then(res => {
      return res.json();
    })
    .then(data => {
      data.forEach(el => {
        if (
          el.codeDepartement == "971" &&
          el.nom
            .toUpperCase()
            .startsWith(ville_input.value.toUpperCase().trim())
        ) {
          if (!ville_arr.includes([el.nom, el.codesPostaux[0]])) {
            ville_arr.push([el.nom, el.codesPostaux[0]]);
            console.log(ville_arr);
          }
        }
      });
    })
    .catch(err => {
      // Doing stuff
    });
});

First of all I want to push each results as arrays into an array like so :

ville_arr.push([el.nom,el.codesPostaux[0]])

My issue is that I get duplicate items into my array when the API fetch the same result, that is why I tried this :

if(!ville_arr.includes([el.nom,el.codesPostaux[0]])){

    ville_arr.push([el.nom,el.codesPostaux[0]])
    console.log(ville_arr)

      }

But I still get duplicate items in the end, I guess it has something to do with the indexes of the array which are unique ? maybe something else ?

Here's a result example

4 Answers

Array.prototype.includes does a referential equality check for objects.

This means that, even though you are pushing objects of the same shape, they are not the same references because each request creates a new object.

The usual pattern is to remember some uniquely identifying part of an object instead, like an id.

Maybe you can store and check for the zipcode instead?

if (!zipcodes.includes(el.codesPostaux[0])) {
  zipcodes.push(el.codesPostaux[0]);
  ville_arr.push([el.nom, el.codesPostaux[0]]);
}

A time-efficient method is to use a set of zipcodes instead of an array (because set lookup time is O(1)):

if (!zipcodesSet.has(el.codesPostaux[0])) {
  zipcodesSet.add(el.codesPostaux[0]);
  ville_arr.push([el.nom, el.codesPostaux[0]]);
}

If you decide to use the ville_arr only, then what you need could also be done using Array.prototype.every (or Array.prototype.some):

// this will return true if every place in ville_arr
// does not have the zipcode from the response
if (ville_arr.every(([, zipcode]) => zipcode !== el.codesPostaux[0])) {
  ville_arr.push([el.nom, el.codesPostaux[0]]);
}

You could also potentially call JSON.stringify over your object to create the same string from a certain object and save that which would work because includes does equality comparison for primitive values such as strings.

Try this:

const ville_input = document.getElementById('search_immobilier_ville');
let ville_arr = [];
ville_input.addEventListener('keyup', () => {
    res_list.innerHTML = '';

    fetch(`https://api.fr/communes?nom=${ville_input.value}`)
        .then(res => {

            // Clear old data if get new response
            ville_arr = [];

            return res.json();
        })
        .then(...)
        .catch(...);
});

OR try use Array.prototype.find() (and extra info about find vs some on jsPerf.com):

if(!ville_arr.find(i => i[1] === el.codesPostaux[0])) {

    ville_arr.push([el.nom, el.codesPostaux[0]]);
    console.log(ville_arr);
}

Array#includes does not compare reference types by their values; it compares them to check whether their references are the same.

For example, this produces false:

var myArr = [[5, 6], [1, 2], [4, 5]];

console.log(myArr.includes([1, 2]));  // false

because the [1, 2] array on line 1 is not the same array as the one on line 3, even though they contain the same values.

To deal with this, you can instead use Array#some, which accepts a lambda function:

var myArr = [[5, 6], [1, 2], [4, 5]];

console.log(myArr.some(e => e[0] === 1 && e[1] === 2));  // true

Or to put this in terms of your specific example:

if(!ville_arr.some(e => e[0] === el.nom && e[1] === el.codesPostaux[0])) {
    ville_arr.push([el.nom,el.codesPostaux[0]])
    console.log(ville_arr)
}

Another "old school" way of doing it could be this: declare an object (o) and add sub-arrays to it with a key generated from the contents of each array. This object will then keep its unique structure by itself:

var o={d:{},add:function(nom,code){this.d[nom+code]=[nom,code]},
get:function(){return Object.values(this.d)}};

You add values to it with

o.add(el.nom, el.codePosteaux[0]);

and retrieve the unique array from it with

o.get()
Related