How to avoid key duplication errors in Vue JS

In one of our projects there was a database bug which basically returned duplicated products for certain searches. This ended up causing a crash in our Vue app where we had a list which used the product sku as a key, something like this:

<li v-for="product in products" :key="product.sku">
  {{ product.name }}
</li>

Since the sku value was not unique there were some errors and the app was no longer working properly in all scenarios.

Fixing the database issue took longer and involved more debugging on the backend team's side. I'm oversimplifying things for the sake of being brief but it was not a standard list of products. But it did have an sku for each and we did use it as a key.

The quick hotfix was to remove the key attribute entirely and push the change to production.

<li v-for="product in products">
  {{ product.name }}
</li>

However, that was not ideal. The purpose of the key attribute is to help Vue's virtual DOM algorithm to identify the virtual nodes when comparing a new list of nodes against the old one. This gives a performance boost and makes things more efficient when you are using dynamic lists which may change on the fly, due to sorting, filtering or reordering which was exactly how we used it.

In the end the backend team fixed the issue and we simply added the key attribute back as it was but I did think of a solution for exactly such cases.

My idea was to use an intermediary function for the key and then take advantage of the memoization technique, albeit a bastardisation of the principle. So instead of using the sku directly, we would do this:

<li v-for="product in products" :key="getKey(product.sku)">
  {{ product.name }}
</li>

Then, inside the getKey method we could keep track of the used values and update them on the fly if they repeat.

getKey: function (key) {
  // we make sure we have a global object where we can keep track of the keys
  if (typeof this._unique_keys_list === 'undefined') {
    this._unique_keys_list = {};
  }

  if (typeof this._unique_keys_list[key] === 'undefined') {
    // if the key is not in use
    // first we mark it as used
    this._unique_keys_list[key] = true;

    // and the we return it as it is
    return key;
  } else {
    // if it is already used, we append something to the end
    key = key + '.' + Math.random();
    // save the new value
    this._unique_keys_list[key] = true;
    // and finally return it
    return key;
  }
}

This approach is not perfect and could lead up to duplicating keys on successive iterations of the products list but it does prevent a crash.

A different approach would be to create a helper function to go through the products list each time it changes, loop the items and check if the values used as a key are indeed unique.

If the duplicated keys are a simptom of truly duplicated items, you can simply remove them from the list. But if they have other differences and only the key attributes are the same, then you need to randomise them. So instead of using, for example, product.sku directly, loop through them and create a product.vue_key which is either the same as product.sku, or a value based on it and then concatenated with something else to make it different.

Anyway, these are pretty much edge cases and more of a food for thought. But it is better to keep them in mind so you'll be prepared if it happens to you.