Why is it better not to use the index of the array as the key in React?
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
is Item
key0
, 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
> is key
of Item 1
corresponding to /code> is 0
, and the key
Item 01
, 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/