In the ReactJS page of Reconciliation, there are two examples:
- an example of the issues that can be caused by using indexes as keys
- an updated version of the same example showing how not using indexes as keys will fix these reordering, sorting, and prepending issues
As of Feb 18, 2020, there is no telling of how to reproduce the issue on the page. I tried clicking on "Add New to Start" or "to End" a few times and reorder the list, and they seemed to work fine. Only later I found out you need to enter some text into the box, and then just "Add New to End", and do it three times, and reorder the list.
In the first example, the text in the input box were not re-ordered. In the second example, the text in the input box were re-ordered as expected.
The two programs differ by using
<ToDo key={index} {...todo} />
vs
<ToDo key={todo.id} {...todo} />
There are also some slightly re-ordering of code and using todoCounter
vs toDoCounter
(capital D
) between the two versions, which I wonder why and the React team might fix it later. But you can modify the first version from key={index}
to key={todo.id}
and you can also see the problem solved.
But then when I looked into the code, the input box doesn't actually add the text data to the state property list
(an array). Only id
and createdAt
are added per new entry to list
.
So while we can say using key={todo.id}
fixed the problem, what was causing the problem in the first place?
You can say that the id
and createdAt
are sorted correctly. What is not sorted correctly are the input boxes... but according to the rule of reconciliation to decide whether to refresh actual DOM elements on the page, the new Virtual DOM subtree is compared to the previous virtual DOM subtree.
Now the "property" of the input box value
is not part of the virtual DOM, unless if React actually silently puts the value
into the virtual DOM.
So when "diff'ing" recursively, React should think the input boxes are all the same. So is this how it worked: if we used the key={index}
, now React diff'ed each column in the current virtual DOM subtree with the previous one, and see that the "ID" and "createAt" cells are different, therefore forcing a refresh to the actual DOM. React saw the input boxes all the same and didn't bother to force a refresh to the actual DOM.
However, if we use key={todo.id}
, now React will think, the whole row is different, because the row "id" has changed. So React will force a refresh to the actual DOM for the whole row, including the input box.
So we can say, this bug occur only when some data is not in the virtual DOM subtree... which is rare, such as in this current case. In other cases, all the data would be given out by the render()
of class component or the return
of function component, and therefore, able to tell ReactJS that, "yes, force a refresh to the actual DOM".
Is this really how it worked?
Using index as keys is very dangerous and is highly advised against.
For example, if you delete an item from the middle of the list, another item will take its place, React needs a unique key to recognize a DOM element as an individual/independent element and not something that is temporary or useless.
Your list is being sorted as intended, you can see that the ID column is sorted accurately.
But in your case, React doesn't know which todo belongs to which ID, indexes are not todo list ID, they're numbers that were generated in order.
Imagine this
the normal order is:
No matter how you sort your list and change its order, if you tell React to use indexes, it will always render it like this:
If you use object IDs, React will keep track of the elements and will connect the keys to the IDs, then render your elements based on the order of your sorted IDs.