Rendering Lists and Keys
Turning arrays into elements, and choosing keys that keep state attached to the right row.
What you'll learn
- Rendering a list with
map - What a key is for, and what React does without one
- Why an index key can corrupt your UI, demonstrated
- How to choose a key that will not bite
Rendering a list
There is no loop syntax in JSX. You turn an array of data into an array of
elements with map, and JSX renders arrays:
Note the parentheses after the arrow: (user) => (<li>...</li>) returns the
element. Braces there would be a function body returning nothing — the mistake
from Arrow Functions.
What a key is for
React needs to match elements in the new render against elements in the previous
one, so it can tell what moved, what was added, and what was removed. The key
is that identity.
Without keys, React falls back to position — which is fine until the list changes order, at which point position is a lie.
Keys must be:
- Unique among siblings (not globally)
- Stable across renders — the same item keeps the same key
- Given to the outermost element returned by the
map
Why an index key is a trap
key={index} is the tempting default, and it is fine for a list that never
reorders, never has items inserted, and holds no state. Otherwise it breaks in a
way that is genuinely confusing to debug.
Here is the failure, side by side. Both lists render the same names. Type something into the first box of each list, then press "Add to front".
In the first list, what you typed stays in the first box while the placeholder shifts down — your text is now attached to the wrong person. In the second, the text moves down with its row.
Nothing about the typed text is in users. It lives in the DOM, and React
decides which input to keep by key. With key={index}, the first row is always
key 0, so React reuses that input for whatever item is now first. With a real
id, React knows the item moved and moves its input with it.
This is the same class of bug as mutating state: the data is right, and the screen is wrong.
Choosing a key
In order of preference:
- A stable id from your data — a database id, a UUID, a slug.
- A value that is genuinely unique and stable — an email, an ISBN.
- The index, only when the list is static: never reordered, never filtered, never inserted into, and its items hold no state.
Do not use Math.random(). A new key on every render tells React the item is
brand new, so it throws away the DOM node and rebuilds it every time — losing
focus, scroll position, and any animation, and doing it slowly.
Keys go on the outermost element
When the mapped item is a fragment, the key still has to go somewhere, so use
the long form <Fragment key={...}>:
import { Fragment } from "react";
{users.map((user) => (
<Fragment key={user.id}>
<dt>{user.name}</dt>
<dd>{user.role}</dd>
</Fragment>
))}
Common mistakes
keyon the wrong element. It goes on whatmapreturns, not on a child inside it.- Index keys in a list that changes. The demo above is what happens.
- Random keys. Every render looks like a brand-new list.
- Expecting keys as a prop.
keyis for React; a component cannot read it. Pass anidprop as well if the child needs the value.
Exercise
This list renders as empty, silently — the map callback uses braces and never
returns, so it produces undefined for every item and React renders nothing.
There is no warning to tell you; an empty list is all you get.
Fix the return, then give each item a proper key.