Why is it better not to use the index of the array as the key in React?

avatar
Mofei Zhu

When you are writing or reading React code by others that renders lists, it is easy to see the use of index as Key, such as:

list.map((row, index)=>(
    <Item key={index} />
))

Looks good and also avoids Key Warning of React. However, doing so is one of the most common React mistakes, or at least it's dangerous! Especially if you add, remove, reorder, or filter your array. Let's look at a simple example:

In this example, we have a list and a button. When the button is clicked, we add a record to the front of the array list.

After running, we enter Item 0 in the input corresponding to Item 0:

But after we click the button, you'll find out. . . .

The first input is still in its original position, why?

Actually, this is because the key corresponding to each item is not fixed when it is rendered, which causes React to think that the input in the first line is always the key=0 one

In other words, initially, for ['Item 0'], the passed when rendering Item key is 0, so there is such a relationship:

- Item key={0}
    - label `Item 0`
    - Input value={Item[key=0].props.text}

When we add an Item, because it is added at the top of the array, it becomes: ['Item 1', 'Item 0']< The key of Item 1 corresponding to /code> is 0, and the keyItem 0 > is 1, the relationship becomes like this:

- Item key={0}
    - label `Item 1`
    - Input value={Item[key=0].children.input}
- Item key={1}
    - label `Item 0`
    - Input value={Item[key=1].children.input}

So React thinks the <input> element of the first Item has not changed (still is the Item[key=0]'s input), so the old <input/> is displayed in the first column (it is already the Item 1 at this time), that is <input> of Item[key=0].

Why is this sometimes works?

In fact, most of our usage scenarios are rendering an Array object that will not change or just adding new items to the end of the array. In these cases, it is relatively safe to use index, because the relationship between the ordering of items and key will not change.

In other words, it is safe to:

  • The contents of the array will not change
  • Arrays will not be filtered
  • Array will not be reordered
  • The array is a LIFO queue (last-in, first-out), that is, the above-mentioned case will only add content at the end of the array

Reverse material

Since index can't be used, can we use random variables, such as Math.random()

// negative teaching material
list.map((row, index)=>(
    <Item key={Math.random()} />
))

Or other similar methods like +new Date() + Math.random(), Symbol() etc?

These methods will cause the key generated each time to be inconsistent, which will force React to think that it has encountered new content and needs to be re-rendered, which will seriously affect the performance of React.

Let's look at another example, change the {index} in the above example to {Math.random()} and try:

After running, we add a few items and enter some values ​​at will:

Then click Add, you will find that all the entered text is gone? :

Yes, this is because every time the key is rendered a new value, React will recreate a new input every time

Example materials

According to React official tips, the best solution is to The item uses a unique ID. For example, if you get the list from the database, you can return the primary key/ID of the database as the unique id; or you can generate a unique and fixed ID locally for each data item by yourself (This is actually not the most recommended practice, given the complexity it brings). The example below, which uses row.uid, is a good example.

list.map((row)=>(
    <Item key={row.uid} />
))

As for the situation that the server can't provide uid, it is necessary to add uid solution when getting data locally (generated by third-party or self-written UUID library). As mentioned above, this operation may bring It adds extra complexity and is not the most recommended practice. The following is a simple example, featData is responsible for obtaining data, and after obtaining the data, we add an independent uid to each piece of data, and then pass it to the list for use:

featchData() {
    // ... return data.list
    let list = data.list.map(item => {
        return {uid: SomeLibrary.generateUniqueID(), value: item};
    });
    setList(list)
}
// ...
<>
     {list.map((row) => (
         return <Item key={row.uid} />;
     ))}
</>

Although it's not the best solution, I recommend a library that generates unique IDs in case of emergency

  • Nano ID https://github.com/ai/nanoid/

References