Back to Blog
ReactKeysPerformanceLists

React Key Prop Explained

Master the React key prop. From list rendering to reconciliation to performance optimization.

B
Bootspring Team
Engineering
May 13, 2020
6 min read

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 items

Choosing 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.

Share this article

Help spread the word about Bootspring