REACT: generating unique keys

I understand that it is best practice when gearing a list of items in REACT to assign a unique, stable, key to each item in order to make redeeming more efficient. I also understand that this in order to be stable, this key should NOT be generated based on something like the item’s index on the source array.

I was wondering if any REACT expert might have a go-to technique for generating stable keys keys , not based on the index , when you have , a generic, non-unique, array?

Example, say you were trying to build a component that renders a UL from the following data array:

let data = [‘HTML’, ‘Bootstrap’,‘javascript’,‘CSS’, ‘javascript’,‘HTML’, ‘javascript’,‘javascript’,‘HTML’, 'CSS,‘PHP’]

As always, I appreciate any insight shared. :slight_smile:

I wouldn’t call myself a React expert by any means, but what I tend do is generate a key using the item, index and string interpolation:

<ul>
  { ['HTML', 'Bootstrap' ,'JavaScript', 'HTML'].map((item, index) => (
    <li key={`${item}-${index}`}>
      {item}
    </li>
  )}
</ul>

What you can also do is use a package like UUID to generate a unique ID.

And for anyone wondering, here is a nice explanation of why using an index as a key is a bad idea (4min 56sec in).

1 Like

Well, as the docs say:

When you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort

The question though is where such data is coming from; if it’s getting created at runtime (from user input, for instance) you might just assign an increment ID to each new item.

Edit: x-post… nice idea to serialize the item value for more stable index-based keys. :-)

1 Like

@James_Hibbard Thank you, for the tips.That method would work in the case the data set only contains unique values, tho. I will look into UUID.

@m3g4p0p, This question arose from doing a simple task tracker demo project. I observed that some low level data that was just an array (which also had repeated values) The data low level data beign read from a database. What I mean by low level is that the info contained in the array are not object literals, and cannot be assigned any sort of permanent id without convoluting the data set.

If you’re sure it only contains unique values, you can use the item itself.

The above will generate unique keys for an array with duplicates:

  • “HTML-0”
  • “Bootstrap-1”
  • “JavaScript-2”
  • “HTML-3”

etc…

it’s the opposite, i am sure it could often contain duplicate values. :frowning: I like m3g4pop’s idea of just having an increment variable; that could be easy to implement in class components but it leves me wondering how to do the same thing with functional components ( as REACT hooks become ever more popular)

Actually I’d just use a plain let id declaration wrapped in a module (for either class or function components):

// inc-id.js
let id = 0;

export function incId() {
  return id++;
}
// my-component.js
export function MyComponent() {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch("/the-data.json")
      .then(res => res.json())
      .then(data =>
        data.map(value => ({
          value,
          id: incId()
        }))
      )
      .then(setData)
      .catch(console.error);
  }, []);

  return (
    <ul>
      {data.map(({ id, value }) => (
        <li key={id}>{value}</li>
      ))}
    </ul>
  );
}

If you want a “fresh” increment ID starting with zero for each component though, a hook might look as follows:

// inc-id.js
import { useCallback } from 'react'

export function useIncId() {
  let id = 0;
  return useCallback(() => id++, []);
}
2 Likes

Yeah, that’s my point. The method I posted above should work with an array containing duplicate values.

Here’s a Pen that demonstrates the problem of using an index as a key (delete an item from the middle of the list to see it).

But when you change to the method I was demonstrating (key={`${item}-${index}`}), things work as expected:

@m3g4p0p: do you see any downside to this approach?

2 Likes

I’m not sure if this has any noteworthy impact, but if you assemble the keys in the render return value, then removing an item would also cause all following keys to change… so wouldn’t it be better to generate IDs upfront? Like e.g.

const allItems = [
  'HTML', 
  'Bootstrap',
  'JavaScript', 
  'HTML'
].map((value, index) => ({ 
  value, 
  id: value + index 
}))

Edit: Scratch that, it won’t let you easily add items later on… you’d have to elaborately compute the new item IDs from the current data, but then you might just as well go back to increment IDs. ^^

1 Like