Course outline

React / Components and state / Lesson 6

Lists, keys, and state identity

Render collections with stable identities and understand why position, component type, and keys determine state preservation.

About 4 min readContent reviewed
On this page

Lesson goals

  • Render a collection from data
  • Keep an edit with its record
  • Verify identity through interactions

Render a collection from data

Use ordinary JavaScript transformations such as map and filter to produce a collection of React elements. Separate selection from presentation when that makes the rule easier to read. Each directly returned sibling in a dynamic list needs an appropriate key at the mapped element boundary. A key is metadata for reconciliation, not a visible DOM id or a normal prop the child can read. Pass the domain id separately when the child needs it. Keep semantic list markup where the information is a list, since React’s ability to return arrays does not supply that HTML relationship automatically.

Keys describe stable sibling identity

A useful key comes from the record’s stable identity and remains the same when its order or editable text changes. It need only be unique among the relevant siblings, not across the whole application. Random keys created during rendering make components remount and can erase local input or focus. Array indices can be unsafe when insertion, deletion, or reordering changes which record occupies a position. An immutable fixed list can have different constraints, but do not turn that special case into a general recommendation. The question is whether the same key continues to mean the same conceptual item over time.

Keep an edit with its record

The first example gives each editable row a record-id key and offers a reverse action. Each row owns a draft initialized from its record’s label. After editing a draft and reversing the array, the draft stays with that id rather than with the old screen position. This demonstrates state identity, not a full synchronization design for incoming server changes. Initializing state from a prop does not automatically update it when that prop later changes. A real editor must decide whether a new record revision preserves, merges, or resets an unsaved draft under a documented policy.

jsx Component module — requires the course React host
import { useState } from "react";
function Row({ item }) { const [draft, setDraft] = useState(item.label); return <li><label>{item.id}<input value={draft} onChange={e => setDraft(e.target.value)} /></label></li>; }
export default function App() { const [items, setItems] = useState([{ id: "a", label: "Alpha" }, { id: "b", label: "Beta" }]); return <main><h1>Stable rows</h1><button type="button" onClick={() => setItems(xs => [...xs].reverse())}>Reverse</button><ul>{items.map(item => <Row key={item.id} item={item} />)}</ul></main>; }
Expected output
After editing row a and reversing, its edited value stays with a.

The draft follows its id through reordering because the keyed Row identity is preserved.

Use keys deliberately to reset state

The second example changes a keyed editor when the selected record changes. React treats the new key as a different subtree and initializes its local state again. This can be exactly right when switching recipients or editing independent records, but it can also discard work unexpectedly. A key is therefore an identity tool, not a cosmetic warning suppressor. State can also reset when component type or position changes. Avoid declaring a component function inside another component when that creates a new component type on every render; define stable component types at module scope. Test the intended preservation and reset transitions explicitly.

jsx Component module — requires the course React host
import { useState } from "react";
function Editor({ name }) { const [draft, setDraft] = useState(name); return <label>Draft<input value={draft} onChange={e => setDraft(e.target.value)} /></label>; }
export default function App() { const [id, setId] = useState("a"); return <main><h1>Record editor</h1><button type="button" onClick={() => setId(x => x === "a" ? "b" : "a")}>Switch record</button><Editor key={id} name={id === "a" ? "Alpha" : "Beta"} /></main>; }
Expected output
Initial draft: Alpha. After Switch record: Beta.

Switching keys intentionally creates a new editor and resets its draft to the selected record’s initial label.

Verify identity through interactions

A snapshot of the initial list cannot reveal most key bugs. Edit one row, insert an item before it, remove a neighbor, reorder the list, and inspect the draft and focus. Also test empty lists and duplicate domain ids, which should be rejected or reconciled before presentation. Stable keys do not fix an incorrect data model that gives two records one identity. Keep filtering and pagination policies clear: removing a keyed subtree from the rendered tree can still unmount it, so key stability alone does not preserve state forever. Lift drafts to an appropriate owner when they must survive that absence.

Common errors and fixes

Edited text moves to another row after sorting

Why it happens
Keys describe positions rather than records.
How to fix it
Use stable domain ids at the mapped boundary.
Prevent it next time
Test edits followed by reorder and insertion.

Every render loses input state

Why it happens
Keys are generated randomly during rendering.
How to fix it
Keep each item’s identity stable.
Prevent it next time
Review key creation independently of display text.

The child cannot read props.key

Why it happens
Key is reserved reconciliation metadata.
How to fix it
Pass the needed id as an ordinary prop.
Prevent it next time
Separate identity metadata from component data.

Check your understanding

1. Why is an editable label a poor key for its own row?

Show worked solution

Changing the label changes the key and tells React to replace the row’s identity, potentially resetting state and focus. Keep a stable record id as the key and treat the label as ordinary mutable domain data updated immutably.

2. When should a draft be lifted above a filtered list?

Show worked solution

Lift it when drafts must survive an item temporarily leaving the rendered list. Stable keys preserve identity across compatible renders, but an unmounted subtree loses its local state. Store drafts by record id in an owner whose lifetime covers the filtering operation.

Key takeaways

  • Keys identify siblings across changes.
  • Identity controls preservation and reset.
  • Test list mutations with edited local state.

Official references