Lists and Keys

An array of elements is a valid child, so a list is just .map(). Each element in that array needs a key: a string or number unique among its siblings that stays attached to the same piece of data across renders. React 7,897 uses keys to match the previous render's elements against the new ones; without one it falls back to position and logs Each child in a list should have a unique "key" prop in development.

The key belongs on the outermost element the callback returns, and it is not a prop — key never appears in props, so a component that needs the value must also receive it under another name. Keys are scoped to one parent, so two different lists may both use key="1".

Rendering a table from an array, keyed by a stable idHTML
<div id="root" style="font:15px system-ui"></div>
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
<script type="text/babel" data-type="module">
import React from 'https://esm.sh/react@19.3.0';
import { createRoot } from 'https://esm.sh/react-dom@19.3.0/client';
const rows = [{ sku: 'KB-01', name: 'Keyboard', qty: 3 },
               { sku: 'MN-14', name: 'Monitor', qty: 0 },
               { sku: 'CB-07', name: 'Cable', qty: 12 }];
const cell = { border: '1px solid #888', padding: '3px 10px' };
createRoot(document.getElementById('root')).render(
  <table style={{ borderCollapse: 'collapse' }}>
    <tbody>{rows.filter(r => r.qty > 0).map(r =>
      <tr key={r.sku}>
        <td style={cell}>{r.sku}</td><td style={cell}>{r.name}</td>
        <td style={{ ...cell, textAlign: 'right' }}>{r.qty}</td>
      </tr>)}</tbody>
  </table>
);
</script>
Browser output of Listing 2.8
Browser output of 8

filter runs before map, and the surviving rows keep their identities because the key comes from the data, not the position. Had the key been the array index, dropping a row would make React reuse row 0's DOM node for what used to be row 1 — harmless for plain text, but it carries over uncontrolled input values, scroll positions, animations and focus. That is the classic "my form fields swapped" bug.

Common candidates for a list key and when each one is acceptable
Key source Safe? Why
Database id, SKU, UUID Yes Stable and unique per item
crypto.randomUUID() in render No New every render; remounts everything
Array index Append-only lists Breaks on insert, delete or sort