React’s key prop plays an integral role in rendering lists. Its usage can seem straightforward – provide a unique key when rendering array data. However, its complexities and the number of different opinions on best practices can often leave us scratching our heads over something seemingly simple. Whenever that’s the case, we need to dive below the surface. In this case, the dive includes learning about two crucial parts of React’s rendering process – reconciliation and diffing. By the end of this article, we will have a comprehensive understanding of React’s key prop, the processes surrounding it, and its optimal usage.
Reconciliation
Reconciliation is the process responsible for maintaining the tree of UI elements. Every time the render function is called, the old and the new tree of elements are compared. Before the DOM is updated, we need a way to know which parts of it have changed – it wouldn’t be efficient to regenerate the entire DOM on every state update. Diffing algorithm determines which parts of DOM need to be regenerated.
Let’s see how reconciliation works in practice. Say we have a list of fruits:
React will compare each item of the two lists and find that the first two match and the third one is new. Instead of removing and re-creating the entire list, the 🍌 item will simply be appended to the existing list node.
But what if we add an item to the beginning instead of the end?
Now what the diffing algorithm sees is 🍎 turned to 🍌, so the existing DOM node is removed and regenerated. Imagine this happening all over your app anytime one of your dynamic lists changes- the overhead could be significant.
In comes React’s key prop
Back to our apples and oranges, with one difference:
Thanks to the key prop, which serves as a unique identifier, the list no longer gets thrown away. The diffing algorithm now has a tool to keep track of elements based on their unique ‘names’. So as long as you make the key unique, there won’t be any harm to your app’s performance, right?
The Pitfalls
Turns out, uniqueness is not a bulletproof vest for our keys. Some ways keys are commonly used cause UI bugs and performance overhead, despite their uniqueness. Here are some examples:
Using index as key: the following example from the official React docs comes with a warning – use as a last resort.
What happens if this list is dynamic? A new item added to the beginning or middle of the list is going to affect the index of other elements, as explained above. This might break your application and display data incorrectly.
There are some exceptions to the rule, as explained by Robin Pokorny in his article on the topic (a highly recommended reading). The most evident one is static lists – there’s no harm in using an index if items won’t be reordered.
Using Math.random() or new Date(): Reaching for Math.random() might seem like a simple way to ensure unique keys. UUID libraries are another alternative often used. Both come with the potential for performance hits that are easy to overlook.
Used inside the render function, the key will generate on every render, causing excessive re-rendering and making it impossible for React to infer these items might be the same.
Misunderstanding specificity requirements: In trying to ensure key uniqueness, overdoing it at the cost of readability is pretty common. Keys like these are unnecessarily specific:
It’s important to remember keys only need to be unique at a component level, not in the entire app.
So how do we make keys unique and stable?
As we’ve established, keys being unique is just a part of it – they also need to be stable, meaning they persist as long as the data doesn’t change. There are a few ways we can accomplish this.
- Use persistent unique identifiers, ideally, attached to rendered item data on creation. Most commonly that would be IDs received from databases.
- Using UUID libraries is a great alternative but as mentioned above, we need to be mindful of causing excessive re-rendering. Say we receive API list data without IDs:
Here we generate unique IDs for each item at the time data is received, making the keys stable as long as we’ve got those items in memory. There are no hidden bugs waiting to happen should this list become re-sortable. In contrast, using uuid() directly inside the render function would cause e.g. dragging the list item to generate a new key and React to lose the reference needed to reconcile the previous and the new tree of elements.
Conclusion
When working with dynamic lists, use IDs attached to data on creation as keys, or generate a unique ID for each item. Make sure generating happens outside the render function to stabilize keys and avoid unnecessary re-renders.
Using an index as a key can be justifiable for static lists although some teams might choose to avoid this altogether. When in doubt, Robin Pokorny lists three conditions components should meet to use indexes safely:
1. The list and items are static–they are not computed and do not change.
2. The items in the list have no ids.
3. The list is never reordered or filtered.