5 Common Mistakes with Keys in React

Li He is a senior engineer and technical architect in the Aladdin Wealth group within BlackRock Solutions

A few years ago, when I asked any frontend developer about React (if they even knew about it), their eyes would light up talking about the Virtual DOM and how it would fundamentally make everything in the web much faster. Indeed, when React came onto the scene, one of their most brilliant contributions was bringing this idea into the mainstream. Now, a virtualized DOM implementation is all but expected in all the major frontend suites, be it React, Angular, VueJS, or a half dozen other libraries.

However, React’s own internals FAQ points out in the first paragraph that virtual DOM isn’t so much a specific technology as it is a pattern built on the back of a fundamental process called reconciliation. Having done my fair share of code reviews, I often see misunderstandings from some developers about how reconciliation works in React, and one of the main ways this misunderstanding manifests itself is through odd use of React keys

As a baseline, lets try to summarize the documentation on keys in React  (or check out the react-reconciler package if you prefer reading code over documentation).

  • When a component node is updated (re-rendered), React compares the children’s keys against the old set of keys.
  • If the child key exists in the NEW set of keys and does not exist on the old set of keys, React creates a NEW child (going through the whole mount + render cycle) at that position in the tree.
  • If the child key is EXISTING in both the old and new sets of keys, React will UPDATE the existing child component at that position in the tree
  • If the child key exists in the old set of keys but DOES NOT EXIST in the new set of keys, it is removed from the tree.

Why do people make mistakes with keys?

A first introduction to keys often goes something like this…

Hi, I’m developer X. I’ve made nifty new component to render up a list, fire up our browser to test it out, and suddenly get hit with this error:
React key error
Oh yeah. Keys. Well, I just need to make it work!

A curious developer might click the error link and follow it through to read all about keys and reconciliation. For a busier developer who is just concerned about finishing the component and moving on, they might do a quick scan of the page, read the first example, and implement a common sense solution that looks similar enough to it, like this:

Totally understandable. The error message goes away, the developer then moves on with their more important tasks, so problem solved right?

Yes. And no.

If one is creating simple, stateless components, which lists often are (i.e a dropdown), then there was probably no significant impact to how they generated their key. But, for more complex components which involve a significant amount of render work or have internal state such as a checkbox, the React key is linked to the instance of the component, and thus has massive impact on what parts of the component life cycle are actually run as well as the current internal state of the component.

For example, in the “more in-depth explanation” there are passages like

Too often, keys simply become an escape hatch for getting rid of an error when rendering arrays of React components. What ends up being a quick fix to hide an error message can end up affecting render performance, or worse, introducing internal state bugs. The following are common issues which I’ve taken from real life examples spotted during code reviews

5 common mistakes involving keys:

  1. Unnecessary key

    The key is hardcoded into a standalone component like a name. There’s nothing really wrong with doing this, but seeing it may hint that the developer may hold a mistaken assumption about needing a key.  A key which is hardcoded as text and never changes serves no purpose because it’s the diff of keys which drives React behavior to either create new or reuse components. There are only a few valid cases where something like this needs to be done, such as if you are trying to preserve/reuse special known components in arrays across renders.
  2. The key is randomly generated in the render step.

    I’ve seen keys created from Math.random() and UUID generators. A consequence is that every single time a render happens, every component with a key is deleted and remade, thus causing significant performance issues. This mistake usually stems from a misunderstanding of the error message which demands a key be unique. The key does not need to be unique in the entire component tree. It only needs to be unique relative to other children of the parent. It’s better to just reference an actual ID attached to the object you’re iterating on, or use the index (but beware pitfalls of using the index)
  3. Extreme avoidance of using index as key

    Related to above, a developer may have learned from somewhere to avoid using the index (the documentation suggest to use it as a last resort), and becomes so averse to it that they jump through hoops to generate non-indexed keys even when index is a perfectly fine option. For example, you have some simple static text that you just want to render in different elements
  4. Unnecessary key specificity

    The key is a concatenated string like `${name}_label`. It may be legitimate in some cases but, again, usually stems from a misunderstanding about unique keys. Unless you are putting lists parallel to each other in the same rendered element, this just adds unnecessary overhead.
  5. Inappropriate reuse of a stateful component

    This one is not really a key mistake per se, but rather a situation that proper use of keys can solve. Consider a hypothetical complex stateful component which derives much of its initial state from props. When a central prop in the component changes that requires it to re-initialize, complex logic can be used to reset components to their original state, undo animations, etc. but there’s a potentially cleaner solution: just throw the old component away and create a new one. This can be simply accomplished via a key.

Takeaways

One of the great things about React is that you can just pick it up and start using it to build an app, while the virtual DOM implementation is abstracted away and not worried about. A lot of developers didn’t start out methodically learning React, but rather started off as general front-end developers in a larger institution who had dabbled in it while moving from jQuery or Angular. Many also “fell into” React as a result of projects here and there.

They didn’t have to read all about the reconciliation process, and instead picked up React organically through learning-while-doing. This speaks to React’s ease of use and the number of high quality examples and tutorials available. But, eventually, an understanding of how internal reconciliation works is critical to developing truly fast and stable components. Keys are a great first step on this journey.

Li He