Use Array.map to transform a data array into an array of React elements.
React accepts an array of elements as a valid child.
function FruitList({ fruits }) {
return (
<ul>
{fruits.map(fruit => (
<li key={fruit.id}>{fruit.name}</li>
))}
</ul>
)
}
// Usage
const fruits = [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cherry' },
]
<FruitList fruits={fruits} />
Each element in the mapped array must have a key prop so React can track
it across renders.
Rule of thumb: every .map() that produces JSX needs a key on the
outermost returned element. If you forget it, React will warn in development
and may produce incorrect diffs.
key is a special React prop that gives each element in a list a
stable identity. React uses it during reconciliation to match elements
between renders and decide what to update, move, or unmount.
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
// ▲ unique, stable identifier
))}
Without keys, React falls back to positional matching: element 0 in the old list = element 0 in the new list. If you insert or delete items, React may reuse the wrong component instance, leading to bugs like stale state, wrong animation, or incorrect DOM content.
Keys must be:
- Unique among siblings (not globally unique).
- Stable — the same item should have the same key across renders.
Rule of thumb: always key from your data's natural unique identifier (database id, UUID, slug). Never use array index as a default.
Index keys break when the list can be reordered, filtered, or have items inserted/deleted.
// ❌ Index key — dangerous for mutable lists
{items.map((item, i) => <Row key={i} data={item} />)}
// ✅ Stable id key
{items.map(item => <Row key={item.id} data={item} />)}
The problem: React identifies components by key. If you delete item at
index 0, everything shifts down by one — the component at index 1 now has
key 0, so React thinks it's the same component as the old index-0 element.
React will update props but not reset state. This causes:
- Controlled input fields retaining the wrong value.
- Animations starting from the wrong position.
- Incorrect checkboxes or selection states.
Rule of thumb: use index as key only when: the list is read-only and never reordered, items have no stable IDs, and the items have no internal state (pure display).
A good key is:
- Unique among siblings — two siblings can't share a key; siblings in different lists can.
- Stable — the same data item has the same key across every render.
- String or number — React converts numbers to strings internally.
// ✅ Database primary key — best option
<Item key={item.id} />
// ✅ Slug/name if guaranteed unique per list
<Tab key={tab.slug} />
// ✅ Composite key when no single field is unique
<Cell key={`${row.id}-${col.name}`} />
// ❌ Math.random() — new key every render, defeats the purpose
<Item key={Math.random()} />
// ❌ Index — unstable when list mutates
<Item key={index} />
Generating keys with Math.random() or Date.now() forces React to
unmount and remount every item on every render — worse than no key at all.
Rule of thumb: use the data's natural ID. If your data has no ID, generate one at the time the data is created (not at render time).
The key must be on the outermost element returned from the map
callback — the element React is tracking. It does not need to be (and
cannot be) read by the component that receives it.
// ✅ key on the outer element
{products.map(p => (
<ProductCard key={p.id} product={p} />
))}
// ❌ key on an inner element — React doesn't see it for list tracking
{products.map(p => (
<div>
<ProductCard key={p.id} product={p} /> {/* wrong position */}
</div>
))}
// If the callback returns a Fragment, key goes on the Fragment
{products.map(p => (
<React.Fragment key={p.id}>
<dt>{p.name}</dt>
<dd>{p.description}</dd>
</React.Fragment>
))}
Rule of thumb: key goes on the tag you write in the .map() return,
not inside it.
No. Keys only need to be unique among siblings — elements within the same list at the same level. The same key value can appear in a different list elsewhere.
function Page() {
return (
<>
<ul>
{fruits.map(f => <li key={f.id}>{f.name}</li>)}
</ul>
<ul>
{/* key={1} here doesn't conflict with key={1} above */}
{vegetables.map(v => <li key={v.id}>{v.name}</li>)}
</ul>
</>
)
}
React tracks keys per list context, not globally across the component tree.
Rule of thumb: your IDs only need to be unique within the array you're
mapping. Composite keys like ${listName}-${item.id} are redundant unless
you have a single flat array mixing different entity types.
Reconciliation is the process React uses to efficiently update the DOM by comparing the new virtual DOM tree with the previous one. React's diffing algorithm has a key heuristic: elements of different types are always replaced; elements of the same type are updated.
Keys extend this heuristic to lists: React matches old and new elements by key, then decides:
- Same key, same type → update props in place.
- New key → mount a new component.
- Missing key (was present before) → unmount the old component.
- Same key, different position → React can reorder the DOM node efficiently.
Before: [A(key=1), B(key=2), C(key=3)]
After: [C(key=3), A(key=1), B(key=2)]
With keys: React moves DOM nodes — no unmount/remount.
Without keys: React updates all three by position — potentially wrong.
Rule of thumb: correct keys make list operations O(n) instead of O(n²); they also prevent state from leaking to the wrong component when items shift.
React warns in development and exhibits undefined behavior — typically it renders only one of the duplicates and drops the other, or updates the wrong component on subsequent renders.
// ❌ Duplicate keys — unpredictable behavior
{items.map(item => (
<Row key={item.category} data={item} />
// If multiple items share the same category, keys collide
))}
// ✅ Use a truly unique field or compose a unique key
{items.map(item => (
<Row key={item.id} data={item} />
))}
In production, no warning is shown — the bug is silent. This is one reason to run development builds regularly.
Rule of thumb: if you see the "Encountered two children with the same key" warning in the console, your mapping produces non-unique keys — check which field you're using.
No. key is reserved by React and is not forwarded to the component
as a prop. If you try to access props.key inside the component, you'll
get undefined.
function Row({ key, name }) {
// key is always undefined here — React consumed it
return <tr><td>{name}</td></tr>
}
// If the component needs the id, pass it explicitly
{items.map(item => (
<Row key={item.id} id={item.id} name={item.name} />
))}
The same is true for ref — both are meta-props handled by React before
the component sees its props.
Rule of thumb: if a component needs its own identifier, pass it as a
separate prop (e.g. id). Treat key as infrastructure, not data.
Options in rough order of preference:
- Add an ID at the source. If you control the API, add an
idfield. - Generate IDs when data enters the app — not at render time.
// When you receive data, enrich it once
import { randomUUID } from 'crypto'
const items = rawData.map(d => ({ ...d, id: randomUUID() }))
- Composite key from fields that together are unique:
{countries.map(c => (
<CountryRow key={`${c.code}-${c.region}`} country={c} />
))}
- Index as last resort — only if the list is static (never reordered or mutated) and items have no internal state.
Rule of thumb: generate IDs at data-entry time (API response handler,
form submission), not inside .map(). A key generated in .map() is a
new key every render — React unmounts and remounts every item.
Without a key, React falls back to positional diffing, which causes:
Incorrect state association — when items reorder, state (including controlled input values, scroll position, open/closed accordion) follows the position, not the item.
Unnecessary re-renders and DOM mutations — React may update the props of an existing component rather than reorder it, doing more work.
Console warning — in development,
Warning: Each child in a list should have a unique "key" prop.
// List re-ordered from [A, B, C] to [C, A, B]
// Without keys: React updates A→C, B→A, C→B (three updates + DOM writes)
// With keys: React moves DOM nodes (one reorder)
The correctness bug is worse than the performance bug: stale input values or wrong selection state are visible to users.
Rule of thumb: treat missing keys as a bug, not a warning. Fix them before they reach production.
Use an object map or a switch inside the map callback to select the component type dynamically.
const BLOCK_COMPONENTS = {
text: TextBlock,
image: ImageBlock,
video: VideoBlock,
divider: DividerBlock,
}
function PageContent({ blocks }) {
return (
<article>
{blocks.map(block => {
const Block = BLOCK_COMPONENTS[block.type]
if (!Block) return null
return <Block key={block.id} {...block} />
})}
</article>
)
}
Note that Block (uppercase) is required because JSX uses the case to
distinguish between a DOM element (string) and a component (variable reference).
Rule of thumb: the object-map pattern scales better than a switch as the number of types grows, and makes it easy to add new block types in one place.
React matches old element at index N to new element at index N. If items reorder, the component at each position gets the props of the new item but retains the state of the old item — because from React's perspective it's the "same" component (same key = same index).
// Scenario: [Alice, Bob, Carol] → [Carol, Alice, Bob] (sorted)
// With index keys:
// Index 0 was Alice, now Carol → React updates props: Alice→Carol
// But Alice's text input still shows Alice's draft (state is index-0's)
Visually: the displayed name changes, but any controlled input value, open dropdown, or animation state stays — it belongs to the position, not the item.
Rule of thumb: this is the canonical "index key" bug. Any time a list can be sorted, filtered, or have items added/removed, use stable IDs.
The React team's guidance: index as key is safe only when all three conditions hold:
- The list is static — it never reorders or has items inserted/deleted.
- Items have no stable IDs in the data.
- The list is never re-filtered — count and order don't change.
// Acceptable: read-only, ordered, no unique id, no internal state
const STEPS = ['Install', 'Configure', 'Deploy']
{STEPS.map((step, i) => (
<li key={i}>{step}</li>
))}
If the list comes from an API and may change at any time, don't use index.
Rule of thumb: if you have to think about whether index is safe, your data should have IDs. The safe scenarios are narrow; don't give yourself permission to use index keys broadly.
Each level of nesting is its own sibling context. Keys at one level don't
interact with keys at another. However, every .map() at every level
needs its own key.
function Table({ rows }) {
return (
<table>
<tbody>
{rows.map(row => (
<tr key={row.id}> {/* key for row siblings */}
{row.cells.map(cell => (
<td key={cell.colId}>{cell.value}</td> {/* key for cell siblings */}
))}
</tr>
))}
</tbody>
</table>
)
}
Rule of thumb: every .map() call needs a key, at every nesting
level. The keys only need to be unique among siblings at that level —
row.id and cell.colId don't need to be in different namespaces.
Both are special "meta-props" handled by React before the component sees its props, but they serve completely different purposes:
key |
ref |
|
|---|---|---|
| Purpose | List identity for reconciliation | Imperative access to DOM node / component instance |
| Readable by component | No (props.key is undefined) |
Via useRef / forwardRef |
| When to use | Every element in a .map() list |
Direct DOM manipulation, focus management, measuring |
| Value type | String or number | Object from useRef() or callback |
const inputRef = useRef()
// key for list tracking, ref for focus imperatively
{items.map(item => (
<input key={item.id} ref={item.isFocused ? inputRef : null} />
))}
Rule of thumb: key is for React's reconciler (list diffing); ref
is for your code to poke the DOM. They are unrelated features that happen
to both be invisible to props.
When a list item needs to render multiple sibling elements without a
wrapper DOM node, use <React.Fragment key={...}> — the short <>…</>
syntax doesn't support key.
function DefinitionList({ items }) {
return (
<dl>
{items.map(item => (
// Short fragment syntax doesn't accept key — use long form
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</React.Fragment>
))}
</dl>
)
}
If you wrap in a <div> instead, the resulting <dl><div><dt>…<dd>…</div></dl>
is invalid HTML and breaks styling.
Rule of thumb: whenever you need key on a Fragment, use
<React.Fragment key={id}>. It's the only valid way to key a fragment.
React's reconciliation algorithm compares the previous and next virtual DOM trees in a single pass per level. For lists specifically, it builds a key-to-fiber map of the old children, then for each child in the new list it looks up the key:
- Key found, same type → update props, keep the existing fiber (DOM node), move it if position changed.
- Key found, different type → unmount old, mount new.
- Key not found → mount new fiber.
- Old key no longer in new list → unmount.
Old: [<li key="a">, <li key="b">, <li key="c">]
New: [<li key="c">, <li key="a">, <li key="b">]
React: key "c" moved from index 2 → 0, DOM node reused.
keys "a" and "b" shifted, DOM nodes reused.
Result: 0 unmounts, 0 mounts, 3 DOM moves.
Without keys React would compare index-by-index and update all three elements' props even though the content is the same.
Rule of thumb: keys let React reorder DOM nodes cheaply instead of doing redundant prop patches. This is why keys are a performance AND correctness feature.
More Components interview questions
More ways to practice
The self-quiz is live. Get notified when mock interviews and new question packs drop.