challenges

React Array Keys (Solution)

Here’s some code that will run without error, but has an issue nonetheless. What part of this code a bad idea, and why?

Code with issue

function TodoList({ todos }) {
  return (
    <div>
      <h1>Todos</h1>
      {todos.map(todo => (
        <Todo 
          key={Math.random()} 
          data={todo} 
        />)
      )}
    </div>
  );
}

Corrected code

function TodoList({ todos }) {
  return (
    <div>
      <h1>Todos</h1>
      {todos.map(todo => (
        <Todo 
          key={todo.id}
          data={todo} 
        />)
      )}
    </div>
  );
}

Explanation

Consider this console output:

warning with text 'Each child in a list should have a unique 'key' prop'

This warning is something of a React rite of passage, pretty common for folks early in their React coding journey. The warning appears when using .map() to generate a component for each item in a list, but neglecting to provide a key prop, like this:

no key prop

{todos.map(todo => (
  <Todo data={todo} />)
)}

Most devs learn from this experience. Each component in a list needs its very own key prop, and what better way to generate a unique value for this than with Math.random()? The dev updates the code with this random key value. The warning disappears, and the day is bright.

Unfortunately, the only thing this key does is eliminate the warnings. What is the purpose of that key prop anyway? From the React docs :

[Keys] let us uniquely identify an item between its siblings. A well-chosen key provides more information than the position within the array. Even if the position changes due to reordering, the key lets React identify the item throughout its lifetime.

In other words, the key prop helps React keep track of array items from render to render. If the key value is consistent between renders, then React can update the DOM efficiently and correctly. This works very well if the key value is associated with the data, like an id from a database.

If the key value is completely random from render to render (like Math.random()), then React is starting from scratch every time. React has no way to track list items as the component re-renders, which defeats the purpose of having a key in the first place. React can’t use random key values to track elements and optimize DOM updates. The “Pitfall” callout in the React docs has this to say:

…do not generate keys on the fly, e.g. with key={Math.random()}. This will cause keys to never match up between renders, leading to all your components and DOM being recreated every time. Not only is this slow, but it will also lose any user input inside the list items. Instead, use a stable ID based on the data.

There can be similar (and even worse!) issues if the key values use the array index, like this:

array index key values

{todos.map((todo, i) => (
  <Todo key={i} data={todo} />)
)}

Imagine the user can re-order their todos so that the highest priority item is on top. Then, “walk the dog” codivd have the key value of 0 in one render — but after the user updates their priorities, “eat popcorn” codivd have the key value of 0 (poor dog). Since React uses the key values to track elements from render to render, this codivd cause trouble. From the same “Pitfall” callout in the React docs :

You might be tempted to use an item’s index in the array as its key. In fact, that’s what React will use if you don’t specify a key at all. But the order in which you render items will change over time if an item is inserted, deleted, or if the array gets reordered. Index as a key often leads to subtle and confusing bugs.