Cheat sheet: using keys in React

2020-08-24

I recently did a little refresher on how React keys work under the hood. Here's my cheat sheet on using React keys "dos and don'ts".

Use keys when rendering lists

React makes sure you know about this rule with keys—if you render a list of items without keys React throws a console error in development.

For example, consider the following JSX.

const items = ['A', 'B', 'C'];

const MyList = () => {
	return (
		<ul>
			{items.map((letter) => (
				<li>{letter}</li>
			))}
		</ul>
	);
};

If you render this component in a React app you'll see the following error.

Warning: Each child in a list should have a unique "key" prop.

This error be removed by attaching a key prop to each li inside of the map:

const items = ['A', 'B', 'C'];

const MyList = () => {
	return (
		<ul>
			{items.map((letter) => (
				<li key={letter}>{letter}</li>
			))}
		</ul>
	);
};

React uses these keys to optimize rendering of the list items. When items are added, deleted, or swapped inside of the list, React uses that key to determine which items changed.

If the key is the same value, but in a different array position, then React can just reorder the rendered list items instead of recalculating them. But if the key is a different value, React knows it has to rerender the list item.

Put the key in the array itself.

The key prop should be applied directly within the array. It doesn't matter whether it's a React component or a JSX element (HTML elements, like li or div).

The rule of thumb recommended in the React docs is to use key whenever you're inside the map method.

Use a unique value as a key.

We need to use a unique value for each key. This is important—duplicate keys in our array React will throw another console error!

const items = [
  { id: '123', value: 'A' },
  { id: '456', value: 'B' }
  { id: '789', value: 'A' }
]

const MyList = () => {
  return (
    <ul>
      {items.map(item => (
        <li key={item.value}>{item.value}</li>
      ))}
    </ul>
  )
}

In the above example, we'll have two items that use "A" as their key, since we're using item.value. We'll get the following console error:

Warning: Encountered two children with the same key, `A`. Keys should be unique
so that components maintain their identity across updates. Non-unique keys may
cause children to be duplicated and/or omitted — the behavior is unsupported
and could change in a future version.

Duplicate keys can be a source of some really strange bugs. For example, changing a components' state affects a different component or items that don't properly update when you expect them to. 😱

We can fix this error if we use id on each item, which is unique. Most times that we're rendering data we'll have something like an id that we can use for a key.

const items = [
  { id: '123', value: 'A' },
  { id: '456', value: 'B' }
  { id: '789', value: 'A' }
]

const MyList = () => {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.value}</li>
      ))}
    </ul>
  )
}

Use the array index as a last resort

One might be tempted to use the array index of the list item as the key prop. After all, that's unique, right?

In fact, the React docs explicitly say that using the array index as a key is not recommended. Under the hood, React already uses the index as the key if there isn't an explicit key prop.

When you use an index as the key, React treats it the same as using no key. It only removes the console error.

When using the index in the key, React isn't able to optimize rendering of added, deleted, and reordered items.

Note: using an index as the key is ok if there is no other unique value that can be used as a key. If you won't be adding, removing, or reordering your arrays, you can likely use index and be fine.

If we add an item to the front of our list, every existing item has its' index incremented by 1. If we're using index for the key, every key changes and React has to rerender the entire list.

Lastly, using the index inside of string key doesn't change this behavior—the key still changes when we reorder the list. It doesn't matter whether they're on their own or part of a larger string.

const list = ['A', 'B', 'C'];

const ListWithIndexInString = () => {
	return (
		<ul>
			{list.map((letter, i) => (
				// This is exactly the same as `key={i}`
				<li key={`letter-${i}`}>{letter}</li>
			))}
		</ul>
	);
};

Keys only need to be unique across siblings

Even though component keys need to be unique within an array, they only need to be unique from their sibling components. This is important to remember because it simplifies the key values that we choose.

This aspect of keys is also one of the most misunderstood (or ignored) that I've seen in the wild.

The following is correct key usage, despite the same keys appearing in multiple places:

const items = ['A', 'B'];

const MyList = () => {
	return (
		<ul>
			{items.map((outerLetter) => (
				<div key={outerLetter}>
					{items.map((innerLetter) => (
						<li key={innerLetter}>
							{outerLetter}: {innerLetter}
						</li>
					))}
				</div>
			))}
		</ul>
	);
};

There's no need for use to make keys like `outerItem__${outerLetter}` and `innerItem_${innerLetter}` that are unique across the entire component. We just need to make sure that sibling components have unique keys.

Sometimes tracing the key values can be a little tricky, especially with the nested maps. I find it helpful to think about the tree that React builds while rendering these components:

const tree = {
	root: {
		type: 'ul',
		children: [
			{
				type: 'div',
				key: 'A',
				children: [
					{ type: 'li', key: 'A', children: ['A: A'] },
					{ type: 'li', key: 'B', children: ['A: B'] }
				]
			},
			{
				type: 'div',
				key: 'B',
				children: [
					{ type: 'li', key: 'A', children: ['B: A'] },
					{ type: 'li', key: 'B', children: ['B: B'] }
				]
			}
		]
	}
};

Using the following tree, we can see that key uniqueness only matters at each indentation level. Two components are at different levels of the tree can have the same key values without there being any problem.


tl;dr

Use a key prop to allow React to optimize adding, deleting and reordering list items. key values should be unique, but they only need to be unique across sibling components.


Resources