89

Problem is the following:

I have data in form of a list of a few thousand elements. Some of them are duplicates, and there might be the chance of having duplicate keys as well then. Because I have no real "ID" or anything which would give me the opportunity to give all elements their id as unique keys, is it okay to use Math.random() instead?

As far as I understood, the keys are mainly used by react to differentiate the components. I think as far as I don't really have anything to do with the keys in my code, this should go fine? To ensure that there will be no duplicate number I might as well divide two math randoms with each other to get an almost certainly unique key.

Is this a good practice? Can I use this without having to worry about anything?

Sossenbinder
  • 4,852
  • 5
  • 35
  • 78
  • You only have to provide keys if you are generating an array of elements. And in that case you could as well use the iteration counter. – Felix Kling Apr 22 '15 at 20:59
  • Just increment a number as @FelixKling suggested. It's zero risk and simple. – WiredPrairie Apr 22 '15 at 23:59
  • 9
    @FelixKling This is bad advice. Never use loop indices as keys either. The key must be unique and consistent. – ffxsam Apr 18 '17 at 23:34
  • 1
    Yah the reason being is that the key is supposed to be an identifier for the data going into the list item. If the key is not linked to the data in some way, react cannot do any optimisation. – unflores Feb 14 '19 at 10:27

10 Answers10

106

Every time a component's key changes React will create a new component instance rather than update the current one, so for performance's sake using Math.random() will be sub-optimal to say the least.

Also if you'll be reordering your list of components in any way, using the index as key will not be helpful either since the React reconciler will be unable to just move around existing DOM nodes associated with the components, instead it will have to re-create the DOM-nodes for each list item, which, once again will have sub-optimal performance.

But to reiterate, this will only be a problem if you will be re-ordering the list, so in your case, if you are sure you will not be reordering your list in any way, you can safely use index as a key.

However if you do intend to reorder the list (or just to be safe) then I would generate unique ids for your entities - if there are no pre-existing unique identifiers you can use.

A quick way to add ids is to just map the list and assign the index to each item when you first receive (or create) the list.

const myItemsWithIds = myItems.map((item, index) => { ...item, myId: index });

This way each item get a unique, static id.

tl;dr How to choose a key for new people finding this answer

  1. If your list item have a unique id (or other unique properties) use that as key

  2. If you can combine properties in your list item to create a unique value, use that combination as key

  3. If none of the above work but you can pinky promise that you will not re-order your list in any way, you can use the array index as key, but you are probably better of adding your own ids to the list items when the list is first received or created (see above)

Markus-ipse
  • 7,196
  • 4
  • 29
  • 34
  • Thanks. I didn't even know the decision on whether a component is being rerendered or not is based on its key. – Sossenbinder Apr 23 '15 at 06:36
  • 9
    According to react eslint, using the index is not adviable: "It's a bad idea to use the array index since it doesn't uniquely identify your elements. In cases where the array is sorted or an element is added to the beginning of the array, the index will be changed even though the element representing that index may be the same. This results in unnecessary renders." See [no-array-index-key](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-array-index-key.md) – fraxture Oct 12 '17 at 19:52
  • agreeing with @fraxture on this. assigning the index is a bad practice – thisguyheisaguy Sep 03 '18 at 18:02
  • 2
    @thisguyheisaguy if you will be re-ordering your list in anyway it's definitely a bad idea to use index as a key, but if it is a static list of some kind there's no problem with it at all, except that you might see an annoying eslint warning of course :) I'll to edit my post to try to make it clearer – Markus-ipse Sep 04 '18 at 19:45
  • I will aggree with @fraxture as well. In fact I saw that in practice when implementing a requirement to have newly added rows to the top of the table. I ended up with duplicate keys for index 0. – the.1337.house Aug 29 '19 at 16:07
  • 1
    it matters when the id for the key is created. you should NOT do `` but when you receive or create the items like described in this answer, it is perfectly valid to do `myId: math.random()` OR use the index. because that is a one time thing and will not be repeated on every component creation. though using the index here is not great when you plan to add new items later. (for removing and reordering it does not matter) – Welcor Feb 22 '23 at 15:42
18

From react documentation:

Keys should be stable, predictable, and unique. Unstable keys (like those produced by Math.random()) will cause many component instances and DOM nodes to be unnecessarily recreated, which can cause performance degradation and lost state in child components.

mpg
  • 59
  • 5
Boris Zagoruiko
  • 12,705
  • 15
  • 47
  • 79
17

Just implement the following code in your react component...

constructor( props ) {
    super( props );

    this.keyCount = 0;
    this.getKey = this.getKey.bind(this);
}

getKey(){
    return this.keyCount++;
}

...and call this.getKey() every time you need a new key like:

key={this.getKey()}
josthoff
  • 227
  • 2
  • 3
8

Is this a good practice? Can I use this without having to worry about anything?

No and no.

Keys should be stable, predictable, and unique. Unstable keys (like those produced by Math.random()) will cause many component instances and DOM nodes to be unnecessarily recreated, which can cause performance degradation and lost state in child components.

Let me illustrate this with a simple example.

class Input extends React.Component {
  handleChange = (e) =>  this.props.onChange({
    value: e.target.value,
    index: this.props.index
  });
  render() {
    return (
      <input value={this.props.value} onChange={this.handleChange}/>  
    )
  }
};

class TextInputs extends React.Component {
  state = {
    textArray: ['hi,','My','Name','is']
  };
  handleChange = ({value, index}) => {
    const {textArray} = this.state;
    textArray[index] = value;
    this.setState({textArray})
  };
  render(){
  return this.state.textArray.map((txt, i) => <Input onChange={this.handleChange} index={i} value={txt} key={Math.random()}/>)
  // using array's index is not as worse but this will also cause bugs.  
  // return this.state.textArray.map((txt, i) => <Input onChange={this.handleChange} index={i} value={txt} key={i}/>)                 
  };
};

Why can't i type in more than one character in the inputs you ask?

It is because I am mapping multiple text inputs with Math.random() as key prop. Every time I type in a character the onChange prop fires and the parent component's state changes which causes a re-render. Which means Math.random is called again for each input and new key props are generated.So react renders new children Input components.In other words every time you type a character react creates a new input element because the key prop changed.

Read more about this here

Shady Pillgrim
  • 175
  • 1
  • 2
  • 10
5

Other answers say that Math.random() is a bad idea, but that's only half true. It's perfectly acceptable to use Math.random() if we do it when the data is initially received, rather than in the render.

For example:

function SomeComponent() {
  const [items, setItems] = React.useState(() => []);

  React.useEffect(() => {
    fetch('/api/data')
      .then((res) => res.json())
      .then((data) => {
        const itemsWithIds = data.map((item) => {
          return {
            ...item,
            id: Math.random(),
          };
        });

        setItems(itemsWithIds);
      });
  }, []);

  return (
    <div>
      {items.map((item) => (
        <Item key={item.id} item={item} />
      ))}
    </div>
  );
}

Right when I receive the data, I map over each item in the list and append a unique ID with Math.random(). When rendering, I use that previously-created ID as the key.

The most important thing is that we don't re-generate the key on each render. With this sort of setup, we only generate the key once, when we receive the data from our API.

You can use this pattern in just about every circumstance, even for local data. Right before you call the state-setter (or, before you initialize the state hook), you can generate a unique ID for each item.

You can also use crypto.randomUUID() instead of Math.random() to generate a uuid instead of a random number.

Joshua Comeau
  • 2,594
  • 24
  • 25
1

Keys should be stable, predictable, and unique. Unstable keys (like those produced by Math.random()) will cause many component instances and DOM nodes to be unnecessarily recreated, which can cause performance degradation and lost state in child components.

You should add a key to each child as well as each element inside children.

This way React can handle the minimal DOM change.

Please go through the https://reactjs.org/docs/reconciliation.html#recursing-on-children. Here you will get best explanation with example of code.

XPD
  • 1,121
  • 1
  • 13
  • 26
1

I had a use case where I am rendering a list of tiles based on data fetched from a remote API. For example, the data looks like this -

[
  {referrals: 5, reward: 'Reward1'},
  {referrals: 10, reward: 'Reward2'},
  {referrals: 25, reward: 'Reward3'},
  {referrals: 50, reward: 'Reward4'}
]
  • This list can be modified on the client-side where a random entry (tile) in the list can be spliced/removed, or a new entry (tile) can be added at the end of the list.

  • There could be duplicate entries in this list so I cannot create a hash/unique key based on the content of the list entry.

Initially, I tried using array index as a key to render the tiles but in this case what ends up happening is, if I splice the entry at index 3 for example, then the entry at index 4 takes its place at index 3, and for React, since key 3 is intact, while rendering, it just removes the tile at index 4 while keeping the original tile at index 3 still visible, which is undesired behavior.

So based on the above ideas shared by @josthoff and @Markus-ipse I used a client-side self-incrementing counter as a key (Check https://stackoverflow.com/a/46632553/605027)

When data is initially fetched from the remote API, I add a new key attribute to it

let _tiles = data.tiles.map((v: any) => (
  {...v, key: this.getKey()}
))

So it looks like below

[
  {referrals: 5, reward: 'Reward1', key: 0},
  {referrals: 10, reward: 'Reward2', key: 1},
  {referrals: 25, reward: 'Reward3', key: 2},
  {referrals: 50, reward: 'Reward4', key: 3}
]

When adding a new entry (tile), I make another call to this.getKey(). By doing this, every entry (tile) has a unique key and React behaves as intended.

I could've used a random hexadecimal key or UUID generator here but for simplicity I went ahead with self-incrementing counter.

Madhur
  • 2,119
  • 1
  • 24
  • 31
0

MathRandom() is not prefereble.

KEY RULES

  • Unique
  • invariant

index❌ => if you sort, index can change so it's not invariant Math.random()❌ => it also invariant

I personaly use uuid(), uuid() output is also invariant but ajusting timing to set uuid value. like below. I tackling this key problem.

If something wrong or notice something , please tell me! I also want to know better solution!

I hope this infomation help

document

enter image description here

0

As additional point to @Markus-ipse's answer:

2.5 use your own unique id generator

Example:

const nextId = Ract.useRef(0);
...
const addToList = (item) => {
  item.myId = nextId.current++;
  ...
};

This will simply increase ref's value by one each time you addToList and thus generate unique value during life of your component.

Unobic
  • 47
  • 2
-2

I think its not correct to give Math.random() for components keys, reason is when you generate random number it is not guaranteed not to get same number again. It is very much possible that same random number is generated again while rendering component, So that time it will fail.

Some people will argue that if random number range is more it is very less probable that number will not be generated again. Yes correct but you code can generate warning any time.

One of the quick way is to use new Date() which will be unique.

Anil Kumar
  • 2,223
  • 2
  • 17
  • 22
  • 2
    I don't see how this is different to Math.random() since on the next rendering cycle a new timestamp will occur? Surely the key would be unique but not predictable/stable. – the.1337.house Aug 29 '19 at 16:12
  • 1
    new Date() is not guaranteed to produce different timestamp if two items are rendered in quick succession, or within a gap of 1 millisecond which can happen many times. – Buddha May 19 '20 at 21:09