The key prop is crucial for React's reconciliation algorithm. Here's how to use it correctly.
Why Keys Matter#
1// Without keys - React can't track items efficiently
2function BadList({ items }) {
3 return (
4 <ul>
5 {items.map((item) => (
6 <li>{item.name}</li> // Warning: Each child should have a unique "key" prop
7 ))}
8 </ul>
9 );
10}
11
12// With keys - React tracks each item
13function GoodList({ items }) {
14 return (
15 <ul>
16 {items.map((item) => (
17 <li key={item.id}>{item.name}</li>
18 ))}
19 </ul>
20 );
21}
22
23// What happens without keys:
24// 1. React can't identify which items changed
25// 2. May recreate DOM elements unnecessarily
26// 3. Can cause bugs with component state
27// 4. Input values may shift to wrong itemsChoosing Keys#
1// BEST: Unique, stable IDs from data
2function UserList({ users }) {
3 return (
4 <ul>
5 {users.map((user) => (
6 <li key={user.id}>{user.name}</li>
7 ))}
8 </ul>
9 );
10}
11
12// GOOD: Combination of fields for uniqueness
13function CommentList({ comments }) {
14 return (
15 <ul>
16 {comments.map((comment) => (
17 <li key={`${comment.postId}-${comment.id}`}>
18 {comment.text}
19 </li>
20 ))}
21 </ul>
22 );
23}
24
25// OK: Index as last resort (static lists only)
26function StaticNavigation() {
27 const links = ['Home', 'About', 'Contact'];
28
29 return (
30 <nav>
31 {links.map((link, index) => (
32 <a key={index} href={`/${link.toLowerCase()}`}>
33 {link}
34 </a>
35 ))}
36 </nav>
37 );
38}
39
40// BAD: Random values - defeats the purpose
41function BadRandomKeys({ items }) {
42 return (
43 <ul>
44 {items.map((item) => (
45 <li key={Math.random()}>{item.name}</li> // Never do this!
46 ))}
47 </ul>
48 );
49}Index Keys Pitfalls#
1// Problem: Index keys with reorderable lists
2function TodoList({ todos, onToggle, onDelete }) {
3 return (
4 <ul>
5 {todos.map((todo, index) => (
6 <TodoItem
7 key={index} // Bug: state gets mixed up on reorder
8 todo={todo}
9 onToggle={() => onToggle(todo.id)}
10 onDelete={() => onDelete(todo.id)}
11 />
12 ))}
13 </ul>
14 );
15}
16
17// Problem: Index keys with inputs
18function EditableList({ items }) {
19 return (
20 <ul>
21 {items.map((item, index) => (
22 <li key={index}>
23 <input defaultValue={item.text} />
24 {/* Deleting an item causes input values to shift! */}
25 </li>
26 ))}
27 </ul>
28 );
29}
30
31// Solution: Use stable IDs
32function FixedEditableList({ items }) {
33 return (
34 <ul>
35 {items.map((item) => (
36 <li key={item.id}>
37 <input defaultValue={item.text} />
38 </li>
39 ))}
40 </ul>
41 );
42}Generating Keys#
1// Generate IDs when creating items
2let nextId = 0;
3
4function createItem(text) {
5 return {
6 id: nextId++,
7 text,
8 };
9}
10
11// UUID for more robust IDs
12import { v4 as uuid } from 'uuid';
13
14function createItem(text) {
15 return {
16 id: uuid(),
17 text,
18 };
19}
20
21// Crypto for unique IDs
22function generateId() {
23 return crypto.randomUUID();
24}
25
26// Using useId for component-scoped IDs (React 18+)
27import { useId } from 'react';
28
29function FormField({ label }) {
30 const id = useId();
31 return (
32 <>
33 <label htmlFor={id}>{label}</label>
34 <input id={id} />
35 </>
36 );
37}Keys with Fragments#
1// Keys on Fragment shorthand (doesn't work)
2function Columns({ data }) {
3 return (
4 <>
5 {data.map((item) => (
6 // Can't add key to <>...</>
7 <>
8 <td>{item.name}</td>
9 <td>{item.value}</td>
10 </>
11 ))}
12 </>
13 );
14}
15
16// Use React.Fragment with key
17function ColumnsFixed({ data }) {
18 return (
19 <>
20 {data.map((item) => (
21 <React.Fragment key={item.id}>
22 <td>{item.name}</td>
23 <td>{item.value}</td>
24 </React.Fragment>
25 ))}
26 </>
27 );
28}Keys for Component Reset#
1// Force component remount with key change
2function ProfilePage({ userId }) {
3 return (
4 <div>
5 <h1>Profile</h1>
6 {/* ProfileContent resets when userId changes */}
7 <ProfileContent key={userId} userId={userId} />
8 </div>
9 );
10}
11
12function ProfileContent({ userId }) {
13 const [isEditing, setIsEditing] = useState(false);
14 const [draft, setDraft] = useState('');
15
16 // State resets when key changes
17 return (
18 <div>
19 <p>User: {userId}</p>
20 {isEditing && <input value={draft} onChange={e => setDraft(e.target.value)} />}
21 </div>
22 );
23}
24
25// Resetting form on submission
26function Form({ onSubmit }) {
27 const [key, setKey] = useState(0);
28
29 const handleSubmit = (data) => {
30 onSubmit(data);
31 setKey(k => k + 1); // Reset form
32 };
33
34 return (
35 <FormContent
36 key={key}
37 onSubmit={handleSubmit}
38 />
39 );
40}Reconciliation Deep Dive#
1// How React uses keys for reconciliation
2
3// Initial render
4<ul>
5 <li key="a">Alice</li>
6 <li key="b">Bob</li>
7 <li key="c">Carol</li>
8</ul>
9
10// After reorder: React matches by key
11<ul>
12 <li key="c">Carol</li> // Moved from position 2
13 <li key="a">Alice</li> // Moved from position 0
14 <li key="b">Bob</li> // Stayed at same key, different position
15</ul>
16
17// With index keys - React sees different content at each position
18// Initial: [0: Alice, 1: Bob, 2: Carol]
19// After: [0: Carol, 1: Alice, 2: Bob]
20// React thinks position 0 changed from Alice to Carol!
21
22// Nested lists with compound keys
23function NestedList({ categories }) {
24 return (
25 <div>
26 {categories.map((category) => (
27 <section key={category.id}>
28 <h2>{category.name}</h2>
29 <ul>
30 {category.items.map((item) => (
31 <li key={item.id}>{item.name}</li>
32 ))}
33 </ul>
34 </section>
35 ))}
36 </div>
37 );
38}Performance Implications#
1// Keys help React optimize updates
2function OptimizedList({ items }) {
3 return (
4 <ul>
5 {items.map((item) => (
6 <ExpensiveItem key={item.id} item={item} />
7 ))}
8 </ul>
9 );
10}
11
12const ExpensiveItem = React.memo(({ item }) => {
13 // Heavy computation
14 return <li>{computeExpensively(item)}</li>;
15});
16
17// Without stable keys, memo is useless
18// React can't tell which component to reuse
19
20// Measuring performance
21function MeasuredList({ items }) {
22 console.time('render');
23
24 const result = (
25 <ul>
26 {items.map((item) => (
27 <li key={item.id}>{item.name}</li>
28 ))}
29 </ul>
30 );
31
32 console.timeEnd('render');
33 return result;
34}Common Mistakes#
1// Mistake 1: Keys in wrong place
2function WrongKeyPlacement({ items }) {
3 return (
4 <ul>
5 {items.map((item) => (
6 // Key should be on the outermost element in the map
7 <li>
8 <span key={item.id}>{item.name}</span> {/* Wrong! */}
9 </li>
10 ))}
11 </ul>
12 );
13}
14
15// Correct
16function CorrectKeyPlacement({ items }) {
17 return (
18 <ul>
19 {items.map((item) => (
20 <li key={item.id}>
21 <span>{item.name}</span>
22 </li>
23 ))}
24 </ul>
25 );
26}
27
28// Mistake 2: Keys not unique within list
29function NonUniqueKeys({ items }) {
30 return (
31 <ul>
32 {items.map((item) => (
33 <li key={item.category}> {/* Multiple items may share category */}
34 {item.name}
35 </li>
36 ))}
37 </ul>
38 );
39}
40
41// Mistake 3: Changing keys unnecessarily
42function UnstableKeys({ items }) {
43 return (
44 <ul>
45 {items.map((item, index) => (
46 <li key={`${item.id}-${Date.now()}`}> {/* Changes every render! */}
47 {item.name}
48 </li>
49 ))}
50 </ul>
51 );
52}Best Practices#
Key Selection:
✓ Use unique IDs from data
✓ Generate IDs when items are created
✓ Use stable, predictable values
✓ Keep keys unique among siblings
When Index is OK:
✓ Static, never-reordered lists
✓ Items have no state
✓ Items are never filtered/sorted
✓ List is not editable
Performance:
✓ Combine with React.memo
✓ Use stable object references
✓ Avoid computing keys expensively
✓ Profile with React DevTools
Avoid:
✗ Random values as keys
✗ Index keys for dynamic lists
✗ Changing keys unnecessarily
✗ Duplicate keys in same list
Conclusion#
Keys are essential for React's efficient list rendering. Use stable, unique IDs from your data, avoid index keys for dynamic lists, and leverage keys to force component remounts when needed. Proper key usage improves performance and prevents subtle bugs.