Skip to the chapter
←ReactGGroundwork⚛React
React/Beginner/B7
7 / 57 written chapters
/

The cover
  1. B1Setup & JSX
  2. B2Thinking in React
  3. B3Components
  4. B4Props
  5. B5useState
  6. B6Events & conditionals
  7. B7Lists & keys
  8. B8Forms
  9. B9useEffect
  10. B10Lifting state & styling
  11. B11Guided project
  12. ★Cheat page
  1. I1useRef
  2. I2Context
  3. I3useReducer
  4. I4Custom hooks
  5. I5Effects in depth
  6. I6Routing
  7. I7State architecture
  8. I8Memoisation
  9. I9Boundaries & portals
  10. I10Data fetching
  11. I11Forms at scale
  12. I12State libraries
  13. I13Auth
  14. I14TanStack Query
  15. I15Testing
  16. I16TypeScript
  17. I17Animation
  18. I18E2E testing
  19. I19i18n
  20. I20File uploads
  21. I21Real-time UI
  22. I22shadcn/ui
  23. ★Cheat page
  1. A1Fiber
  2. A2Concurrent
  3. A3Suspense
  4. A4Server Components
  5. A5Server Actions
  6. A6use()
  7. A7Rendering strategies
  8. A8Effect timing
  9. A9External stores
  10. A10Refs in depth
  11. A11Component patterns
  12. A12Performance
  13. A13Accessibility
  14. A14Design systems
  15. A15Advanced TypeScript
  16. A16Architecture at scale
  17. A17Custom renderers
  18. A18Migrating & upgrading
  19. A19Production debugging
  20. A20Review & judgement
  21. A21State machines
  22. A22Feature flags
  23. A23Interview bank
  24. ★Cheat page

⚛B7 · Part 1 · Beginner

Lists with .map() and keys

A key is not for you and it is not for performance — it tells React which item is which.

4 min read10 sections5 exercises
7 of 57

In this chapter

  • Rendering a list
  • What a key is for
  • Where to find a key
  • The rules
  • A key is not a prop

and 5 more

about 4 min
10On this page
  1. Rendering a list
  2. What a key is for
  3. Where to find a key
  4. The rules
  5. A key is not a prop
  6. The trick worth knowing
  7. What React does with a key, step by step
  8. Lists inside lists
  9. Filtering and sorting
  10. The empty case

Rendering a list

There is no loop syntax in JSX, because JSX holds expressions and a for loop is a statement. You map an array of data to an array of elements, and React renders arrays by rendering each item.

<ul>
  {tasks.map((task) => (
    <li key={task.id}>{task.title}</li>
  ))}
</ul>

Watch the brackets: ( after the arrow returns the JSX. Using { opens a function body, and without a return it produces undefined and an empty list — a five-minute bug everyone hits once.

What a key is for

When a list re-renders, React has an old array of elements and a new one. It needs to decide, for each new element, whether it corresponds to an existing DOM node it can update, or a new node it must create. The key is that identity.

Without keys, React falls back to position: first item to first item, second to second. That is correct only if the list never reorders, never has items inserted, and never has items removed from anywhere but the end.

Why index keys break
{items.map((item, i) => <Row key={i} item={item} />)}

Start with [A, B, C] — keys 0, 1, 2. Delete A. Now you have [B, C] with keys 0, 1. React looks at key 0: it existed before, so it keeps that DOM node and just changes the props from A to B.

Everything React was told to preserve is preserved on the wrong item: text typed into an input, a checked checkbox, focus, scroll position, and any state inside that row. The list looks right until a row has state, and then it is baffling.

Where to find a key

SituationKey
Data from a database or APIThe record's id. Always.
Created in the browserGenerate one at creation: crypto.randomUUID()
A genuinely static listThe index is fine — but so is not looping at all
No natural idA stable field or combination, e.g. country + "-" + city

Generate ids when the item is created, not while rendering. key={Math.random()} or key={crypto.randomUUID()} inside map produces a new key every render, so React throws away every DOM node and rebuilds the entire list each time — worse than no key at all.

The rules

  • Unique among siblings, not globally. Two different lists can both use key 1.
  • Stable across renders. The same item keeps the same key for its whole life.
  • On the outermost element of the mapped item — the thing map returns.
{rows.map((row) => (
  <Fragment key={row.id}>      // ✓ key on the fragment
    <dt>{row.term}</dt>
    <dd>{row.def}</dd>
  </Fragment>
))}

The short <> syntax cannot take a key, so import Fragment when you need one.

A key is not a prop

function Row({ key, item }) {   // ✗ key is always undefined here
function Row({ id, item }) {    // ✓ pass it again if you need it

<Row key={item.id} id={item.id} item={item} />

React consumes key itself; it never reaches the component.

The trick worth knowing

Changing a key deliberately is how you tell React "this is a different thing now" — it unmounts the old component and mounts a fresh one, resetting all of its state.

<ProfileForm key={userId} user={user} />

Switch users and the form resets, without a single effect watching userId. This is the intended answer to "how do I reset state when a prop changes", and it is much less code than the alternative.

What React does with a key, step by step

When it reconciles a list, React builds a map of the old children by key, then walks the new list:

  1. Key exists in the old map and the element type matches → update that DOM node in place, keeping its state.
  2. Key exists but the type changed → unmount the old, mount a new one.
  3. Key is new → mount.
  4. Old key not claimed by anything → unmount.

Reordering a keyed list therefore moves DOM nodes rather than rebuilding them, which is why a keyed list keeps input values and focus across a sort and an index-keyed one does not.

Lists inside lists

{groups.map((group) => (
  <section key={group.id}>
    <h3>{group.name}</h3>
    <ul>
      {group.items.map((item) => (
        <li key={item.id}>{item.label}</li>    // only unique within this ul
      ))}
    </ul>
  </section>
))}

Keys are scoped to siblings, so the inner list does not need group.id + item.id — the items are only ever compared with each other.

Filtering and sorting

{tasks
  .filter((t) => !t.done)
  .sort((a, b) => a.due - b.due)
  .map((t) => <Row key={t.id} task={t} />)}

This is safe because filter already made a copy — sort mutates, so sorting tasks directly would mutate your state in place. If there is no filter in the chain, copy first: [...tasks].sort(...).

Keep the pipeline readable. Once it grows past two or three steps, pull it out into a named variable above the JSX — and if it is expensive, into useMemo.

The empty case

{tasks.length === 0 ? (
  <p className="empty">Nothing due. Add a task to get started.</p>
) : (
  <ul>{tasks.map((t) => <Row key={t.id} task={t} />)}</ul>
)}

[].map() renders nothing at all, which is silently wrong — the user sees a blank area and cannot tell whether it is loading, broken, or genuinely empty.

Interview answer, one sentence

"Keys give React item identity between renders so it can match elements to existing DOM nodes. Index keys break the moment the list reorders or an item is removed from the middle, because React then preserves state on the wrong row — and changing a key on purpose is the idiomatic way to reset a component."

05Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 9 testsFind the keys that will collidebeginner
  • 9 testsMove an item without mutatingbeginner
  • 8 testsA list that keeps notes with their itemsbeginner
  • 9 testsNested comments with repliesintermediate
  • 7 testsOne key per group, one key per itembeginner
All 46 for this level →
B7

Finished reading?

0 of 57 chapters read in this series

→Next stop

  1. B6Previous · B6Events & conditional rendering
  2. B7you are here
  3. Next · B8Controlled formsThe input shows what state says, and typing asks state to change.4 min read

01On this page

10 sections · 0%

  1. 01Rendering a list
  2. 02What a key is for
  3. 03Where to find a key
  4. 04The rules
  5. 05A key is not a prop
  6. 06The trick worth knowing
  7. 07What React does with a key, step by step
  8. 08Lists inside lists
  9. 09Filtering and sorting
  10. 10The empty case

[] chapters · t top