Keys in the First List
map creates sibling elements from data. React needs a stable way to match each previous sibling with the corresponding next sibling after the array changes.
lessons.map((lesson) => (
<LessonItem key={lesson.id} lesson={lesson} />
));The key says which lesson identity this element represents among the siblings returned by the same mapping operation.
React uses the key together with element type and parent position during reconciliation. Reconciliation is React's process for matching previous and next element trees before commit.
Follow One Update Without Keys
Begin with three lessons.
position 0 -> Expressions
position 1 -> Attributes
position 2 -> FragmentsInsert a new lesson at the start.
position 0 -> JSX Overview
position 1 -> Expressions
position 2 -> Attributes
position 3 -> FragmentsWithout explicit keys, React falls back to position for this sibling sequence. The previous element at position zero can be matched with the next element at position zero even though the underlying lesson changed from Expressions to JSX Overview.
For static text, React can update the visible content and the result can appear correct. A real item can hold component state, an active input, focus, animation state, or subscriptions. Matching the old instance with another lesson lets that retained state follow the position instead of the lesson.
Stable data keys change the match.
jsx-overview -> JSX Overview
expressions -> Expressions
attributes -> Attributes
fragments -> FragmentsExpressions remains the element with key expressions even after moving to another array position.
A key identifies one sibling element across renders. It should stay with the same data record when that record moves.
Choose an ID from the Data
Use an ID assigned when the record was created or stored.
const lessons = [
{
id: "lesson_01J6Q6X7Y8",
title: "Keys in the First List",
},
];<LessonItem key={lesson.id} lesson={lesson} />A permanent slug can also work when it is unique among siblings and does not change during the record's lifetime.
<LessonItem key={lesson.slug} lesson={lesson} />Do not use a display title when editors can rename it. A title change would also change the key, causing React to treat the item as another instance.
The best key comes from the identity rules of the data source. Database primary keys, stable API IDs, and locally generated record IDs are normal sources.
Generate Local IDs at Record Creation
Create an ID when a new local record is created.
const newLesson = {
id: crypto.randomUUID(),
title: "Untitled lesson",
};Store that object in the application data. Later renders read the same id.
Do not generate the ID inside map.
lessons.map((lesson) => (
<LessonItem
key={crypto.randomUUID()}
lesson={lesson}
/>
));Every render gives the same records new keys. React cannot match them with their previous instances. It removes the previous items and creates new ones.
Consequences can include these results.
- local item state resets
- uncontrolled input values reset
- focused elements lose focus
- DOM nodes are recreated
- effect cleanup and setup run again later
The ID generator can produce excellent unique values and still be wrong in this location. Stability across renders is the missing property.
Generate an ID once when data is created. Do not generate a key while rendering existing data.
The Array Index Describes Position
map supplies the current index as a second callback argument.
lessons.map((lesson, index) => (
<LessonItem key={index} lesson={lesson} />
));The index is stable only while every earlier position remains unchanged. Inserting at the start changes every later index. Sorting changes the index for moved records. Deleting shifts all following records.
An index key can be acceptable for a fixed list that never inserts, removes, reorders, filters, or gives its items persistent state. Those conditions often change as a feature grows, while a data ID remains correct.
Use the ID when one exists.
<LessonItem key={lesson.id} lesson={lesson} />Do not combine index and ID without a reason.
key={`${lesson.id}-${index}`}The changing index makes the combined key change when the item moves, defeating the stable ID.
Keys Need Uniqueness Only Among Siblings
Two different lists can reuse the same local IDs.
function PublishedList({ lessons }) {
return lessons.map((lesson) => (
<LessonItem key={lesson.id} lesson={lesson} />
));
}function DraftList({ lessons }) {
return lessons.map((lesson) => (
<LessonItem key={lesson.id} lesson={lesson} />
));
}The keys in PublishedList are compared with other siblings in that returned list. React compares DraftList keys in a separate sibling set.
Keys do not need application-wide uniqueness. They need uniqueness among the elements produced under one parent.
Duplicate sibling keys make matching ambiguous.
key lesson-1 -> first element
key lesson-1 -> second elementReact reports a warning and update behavior can duplicate, omit, or mismatch items. Repair the data identity rather than adding a random suffix at render time.
Put the Key on the Direct Mapped Element
This mapping returns LessonItem elements.
lessons.map((lesson) => (
<LessonItem key={lesson.id} lesson={lesson} />
));Place the key there because React compares those sibling component elements.
Putting a key on the li inside LessonItem does not identify the outer siblings soon enough.
function LessonItem({ lesson }) {
return <li key={lesson.id}>{lesson.title}</li>;
}The parent array contains LessonItem elements with no keys. React needs the key at the mapping site.
function LessonItem({ lesson }) {
return <li>{lesson.title}</li>;
}<LessonItem key={lesson.id} lesson={lesson} />Add the key to the outermost element returned directly by the array callback.
Key a Group with Fragment
One data record can produce several sibling host nodes.
import { Fragment } from "react";terms.map((term) => (
<Fragment key={term.id}>
<dt>{term.name}</dt>
<dd>{term.definition}</dd>
</Fragment>
));The fragment is the direct mapped element and receives the key. Both host children remain associated with the same term record.
The short fragment syntax cannot receive props.
<>
<dt>{term.name}</dt>
<dd>{term.definition}</dd>
</>Use the named Fragment form for a keyed group.
key Is Not a Normal Component Prop
React reads key while matching elements and does not pass it to the component as an ordinary prop.
function LessonItem({ key, lesson }) {
return <li>{key} {lesson.title}</li>;
}The destructured key does not receive the special element key. React can report a warning when code tries to read it through props.
Pass a separate identifier prop when the component needs the same data.
<LessonItem
key={lesson.id}
lessonId={lesson.id}
lesson={lesson}
/>function LessonItem({ lessonId, lesson }) {
return <li data-lesson-id={lessonId}>{lesson.title}</li>;
}The two props have different owners. React uses key internally. The component uses lessonId according to its public contract.
Keys and DOM IDs Are Separate
A key does not appear as an HTML id.
<LessonItem key={lesson.id} lesson={lesson} />If the host element needs an ID or data attribute, pass and apply it explicitly.
<li id={`lesson-${lesson.id}`}>
{lesson.title}
</li>DOM IDs must be unique in the document and follow selector and association requirements. React keys only need sibling uniqueness and never become DOM attributes by themselves.
Composite Keys Need Stable Parts
Some data has identity defined by more than one field. A reader's lesson record might use both IDs.
const key = `${readerId}:${lessonId}`;This can work when both values are stable and the separator cannot create collisions in the data format.
Prefer a stored record ID when the backend already assigns one. Constructed keys duplicate identity rules in the UI and can become incorrect when those rules change.
Never include display state in the key.
key={`${lesson.id}:${lesson.complete}`}Marking the lesson complete changes the key and recreates the item. Completion is a property of the same lesson, not another lesson identity.
A Changed Key Creates Another Instance
React treats a different key as another element identity at that parent.
<LessonEditor key={lesson.id} lesson={lesson} />Selecting another lesson.id creates another editor instance and resets its local state. Changing the key is appropriate here because the editor now represents a different record.
For list items, preserve the key to preserve the record's instance. For an intentional full reset, change it because the represented identity truly changed.
Do not change a key merely to force a visual update. Repair an incorrect state or prop at its data source.
Inspect a Key Failure
Render a list using index keys and place an uncontrolled text input in each item.
function LessonItem({ lesson }) {
return (
<li>
{lesson.title}
<input defaultValue={lesson.note} />
</li>
);
}Type into the first input, then insert a lesson at the beginning. With index keys, the retained first DOM input can now sit beside the new lesson because React matched position zero with position zero.
Replace the key with lesson.id and repeat. React can move the instance representing the original lesson while creating one for the inserted record.
The temporary uncontrolled input exposes the matching behavior with the smallest possible example.
Check List Identity
Test every list key against four requirements.
unique among these siblings
stable across renders
derived from the record identity
placed on the element returned by mapSource Notes
- React documentation, Rendering Lists
- React documentation, Preserving and Resetting State
- React documentation, Special Props Warning
- MDN,
crypto.randomUUID()