Must knowReact Fundamentals · 10

Rendering Arrays

Keys give list items an identity. A cart with gift notes shows what goes wrong with index keys, and how a key resets an input.

On this page · 8 sections
  1. In one minute
  2. The example: a cart with gift notes
  3. Rules for keys
  4. key as a reset button
  5. Common mistakes
  6. Interview Q&A
  7. Related
  8. Sources

In one minute

When you render a list with .map(), React has to work out, on the next render, which new item corresponds to which old one. A key answers that: it’s the item’s identity among its siblings. With a stable id as the key, React moves, keeps and deletes the right rows. With no key or the array index, React matches by position, which goes wrong as soon as items are added, removed or reordered: anything the DOM remembers (typed text, focus, a component’s state) stays at the old position and ends up on the wrong item.

You’ll be able to: choose a key, explain why index keys break, and use key on purpose to reset a component.

key={index}
Cart.tsx
1function Cart() {2  const [items, setItems] = useState(initial)3  return items.map((item, index) => (4    <li key={index}>5      <span>{item.name}</span>6      <input placeholder="Gift note" />7      <button onClick={() => remove(item.id)}>Remove</button>8    </li>9  ))10}
What you see
Ceramic Mug · note: “Gift wrap, please”
Wireless Headphones · note: (empty)
Trail Backpack · note: (empty)
  1. event<button onClick={() => remove(item.id)}>Remove</button>Click Remove on the Ceramic Mug. The list now has two items: Headphones, Backpack.
  2. render phase<li key={index}>With index keys, the Headphones now have key 0, the key the Mug’s row had. So React thinks row 0 is still the same row.
  3. commit phase<span>{item.name}</span>React keeps row 0’s DOM, its input included, and only changes the text. The last row is deleted.
  4. The Mug’s gift note now sits next to the Headphones. Recorded: { item: "Wireless Headphones", note: "Gift wrap, please" }. No key at all gives the same wrong result.

0 / 0
A gift note is typed on the first cart item, then that item is removed. The key decides whose note survives (both recorded).

The example: a cart with gift notes

function Cart({ keyBy }: { keyBy: 'index' | 'id' | 'none' }) {
	const [items, setItems] = useState(initial)
	const remove = (id: string) => setItems((list) => list.filter((p) => p.id !== id))

	return (
		<ul className="cart">
			{items.map((item: Product, index) => (
				<li key={keyBy === 'id' ? item.id : keyBy === 'index' ? index : undefined}>
					<span>{item.name}</span>
					<input placeholder="Gift note" />
					<button onClick={() => remove(item.id)}>Remove</button>
				</li>
			))}
		</ul>
	)
}

The recorder typed “Gift wrap, please” next to the Ceramic Mug, then removed the Mug. The rows afterwards:

With key={item.id}:

[
  {
    "item": "Wireless Headphones",
    "note": ""
  },
  {
    "item": "Trail Backpack",
    "note": ""
  }
]

With key={index}:

[
  {
    "item": "Wireless Headphones",
    "note": "Gift wrap, please"
  },
  {
    "item": "Trail Backpack",
    "note": ""
  }
]

With no key at all (React also warned):

[
  {
    "item": "Wireless Headphones",
    "note": "Gift wrap, please"
  },
  {
    "item": "Trail Backpack",
    "note": ""
  }
]
Each child in a list should have a unique "key" prop.

Only the id version is right. With index keys, the Headphones got key 0, the key the Mug’s row used to have, so React kept that row (and its input with the note) and just changed its text. No key behaves exactly like index keys, plus a warning.

Rules for keys

key as a reset button

Changing an element’s key tells React “this is a different element”: it unmounts the old one (and its state) and mounts a fresh one. That’s an easy way to reset a component without writing reset logic:

function Coupon() {
	const [resetKey, setResetKey] = useState(0)
	return (
		<>
			<input key={resetKey} id="coupon" placeholder="Coupon code" />
			<button id="clear" onClick={() => setResetKey((k) => k + 1)}>Clear</button>
		</>
	)
}
{
  "logs": [],
  "warnings": [],
  "before": "SAVE10",
  "after": ""
}

The same trick clears a file input, or resets a whole form or profile page when the selected product id changes: <ProductEditor key={productId} />.

Common mistakes

Interview Q&A

Why does React need a key when rendering arrays?

To match items between renders. Without identity, a removed first item looks the same as “every item’s content shifted up by one”, and React updates by position. Keys let it keep, move or delete the element that actually belongs to each item.

Why is the array index usually a bad key?

The index is the position, so it gives React no new information. Recorded: after removing the Mug with index keys, its gift note stayed on row 0, which now showed the Headphones.

What makes a valid key?

Unique among the siblings in that list, and stable for the same item across renders. Usually an id from the data.

What else can changing a key be used for?

Resetting state: a new key unmounts the old element and mounts a new one. Recorded: a coupon input with key={resetKey} went from "SAVE10" to "" when the key changed.

Sources

Primary source: react.dev/learn/rendering-lists