Keys in React
Keys in React
Keys are special attributes that help React identify which items in a list have changed, been added, or been removed. They are crucial for efficient list rendering and maintaining component state.
Why Keys Matter
React uses keys to:
- Track which items have changed
- Preserve component state during re-renders
- Optimize rendering performance
- Maintain correct animations and transitions
// Without keys, React may:
// - Lose component state
// - Show incorrect data
// - Have poor performance
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li> // Key helps React track this item
))}
</ul>
);
}
Basic Key Usage
Adding Keys to List Items
function NumberList() {
const numbers = [1, 2, 3, 4, 5];
return (
<ul>
{numbers.map(number => (
<li key={number.toString()}>
{number}
</li>
))}
</ul>
);
}
Keys with Objects
function UserList({ users }) {
return (
<div>
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}
function UserCard({ user }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
What Makes a Good Key
1. Unique Among Siblings
Keys must be unique among siblings, but don't need to be globally unique:
function CategoryList({ categories }) {
return (
<div>
{categories.map(category => (
<div key={category.id}>
<h2>{category.name}</h2>
<ul>
{category.items.map(item => (
// Keys only need to be unique within this list
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
))}
</div>
);
}
2. Stable
Keys should remain the same between renders:
// ❌ Bad - Math.random() changes on every render
{items.map(item => <li key={Math.random()}>{item}</li>)}
// ✅ Good - ID remains stable
{items.map(item => <li key={item.id}>{item.name}</li>)}
3. Predictable
Keys should be deterministic and not change unexpectedly:
// ❌ Bad - Date.now() creates new key each time
{items.map(item => <li key={Date.now()}>{item}</li>)}
// ✅ Good - Use existing unique property
{items.map(item => <li key={item.uuid}>{item.name}</li>)}
Common Key Strategies
Using IDs from Data
const todos = [
{ id: 'todo-1', text: 'Learn React' },
{ id: 'todo-2', text: 'Build an app' }
];
function TodoList() {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
Generating IDs
import { v4 as uuidv4 } from 'uuid';
function ItemManager() {
const [items, setItems] = useState([]);
const addItem = (text) => {
const newItem = {
id: uuidv4(), // Generate unique ID
text: text,
createdAt: new Date()
};
setItems([...items, newItem]);
};
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}
Composite Keys
function Grid({ rows, columns }) {
return (
<div className="grid">
{rows.map((row, rowIndex) => (
<div key={`row-${rowIndex}`} className="row">
{columns.map((col, colIndex) => (
<Cell
key={`${rowIndex}-${colIndex}`}
row={rowIndex}
col={colIndex}
/>
))}
</div>
))}
</div>
);
}
When Index as Key is Acceptable
Using array index as key is only safe when:
- List items have no stable IDs
- List is static (won't reorder)
- List items won't be filtered
- No component state in list items
// ✅ OK for static lists
const staticItems = ['Home', 'About', 'Contact'];
function Navigation() {
return (
<nav>
{staticItems.map((item, index) => (
<a key={index} href={`#${item.toLowerCase()}`}>
{item}
</a>
))}
</nav>
);
}
Problems with Index as Key
Lost Component State
// ❌ Problem: Input values get mixed up when reordering
function BadExample() {
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
const reverse = () => setItems([...items].reverse());
return (
<div>
<button onClick={reverse}>Reverse</button>
{items.map((item, index) => (
<div key={index}>
<span>{item}</span>
<input type="text" placeholder="Enter value" />
</div>
))}
</div>
);
}
// ✅ Solution: Use stable IDs
function GoodExample() {
const [items, setItems] = useState([
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' }
]);
const reverse = () => setItems([...items].reverse());
return (
<div>
<button onClick={reverse}>Reverse</button>
{items.map(item => (
<div key={item.id}>
<span>{item.text}</span>
<input type="text" placeholder="Enter value" />
</div>
))}
</div>
);
}
Animation Issues
// Keys help maintain smooth animations
function AnimatedList({ items }) {
return (
<TransitionGroup>
{items.map(item => (
<CSSTransition
key={item.id} // Stable key ensures smooth transitions
timeout={300}
classNames="fade"
>
<div>{item.name}</div>
</CSSTransition>
))}
</TransitionGroup>
);
}
Keys in Fragments
When returning multiple elements, keys can be added to fragments:
function GlossaryList({ items }) {
return (
<dl>
{items.map(item => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}
// Or with shorthand syntax (if supported)
function GlossaryList({ items }) {
return (
<dl>
{items.map(item => (
<> key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</>
))}
</dl>
);
}
Advanced Key Patterns
Maintaining Focus
function EditableList({ items }) {
const [editingId, setEditingId] = useState(null);
return (
<ul>
{items.map(item => (
<li key={item.id}>
{editingId === item.id ? (
<input
autoFocus
defaultValue={item.text}
onBlur={() => setEditingId(null)}
/>
) : (
<span onClick={() => setEditingId(item.id)}>
{item.text}
</span>
)}
</li>
))}
</ul>
);
}
Preserving Scroll Position
function MessageList({ messages }) {
const listRef = useRef(null);
const [scrollPositions, setScrollPositions] = useState({});
const saveScrollPosition = (id) => {
if (listRef.current) {
setScrollPositions(prev => ({
...prev,
[id]: listRef.current.scrollTop
}));
}
};
return (
<div ref={listRef} className="message-list">
{messages.map(message => (
<Message
key={message.id} // Stable key preserves component instance
message={message}
onScroll={() => saveScrollPosition(message.id)}
/>
))}
</div>
);
}
Debugging Key Issues
React DevTools
Use React DevTools to inspect keys:
- Open React DevTools
- Select a list component
- Check if keys are unique and stable
- Look for key warnings in console
Common Warning Messages
// Warning: Each child in a list should have a unique "key" prop
function BadList() {
return (
<ul>
{items.map(item => <li>{item}</li>)} {/* Missing key */}
</ul>
);
}
// Warning: Encountered two children with the same key
function DuplicateKeys() {
return (
<ul>
<li key="1">First</li>
<li key="1">Second</li> {/* Duplicate key */}
</ul>
);
}
Performance Implications
Without Proper Keys
// React has to:
// 1. Destroy all list item components
// 2. Create new components
// 3. Lose all component state
// 4. Re-run effects
With Proper Keys
// React can:
// 1. Match existing components with new data
// 2. Update only changed components
// 3. Preserve component state
// 4. Skip unnecessary effects
Best Practices
1. Use Stable, Unique IDs
// ✅ Good
{users.map(user => <UserCard key={user.id} user={user} />)}
// ❌ Bad
{users.map((user, i) => <UserCard key={i} user={user} />)}
2. Don't Generate Keys During Render
// ❌ Bad - new key every render
{items.map(item => <Item key={Math.random()} item={item} />)}
// ✅ Good - generate ID when creating item
const addItem = (text) => {
setItems([...items, { id: uuidv4(), text }]);
};
3. Keep Keys at the Top Level
// ❌ Bad - key on inner element
{items.map(item => (
<li>
<span key={item.id}>{item.text}</span>
</li>
))}
// ✅ Good - key on outer element
{items.map(item => (
<li key={item.id}>
<span>{item.text}</span>
</li>
))}
4. Use Meaningful Keys
// ✅ Good - descriptive composite key
<Cell key={`cell-${row}-${col}`} />
// ❌ Less clear
<Cell key={`${i}-${j}`} />
Summary
Keys are essential for:
- Efficient list rendering
- Preserving component state
- Smooth animations
- Correct component updates
Remember:
- Keys must be unique among siblings
- Keys should be stable across renders
- Use IDs from your data when possible
- Avoid array index as key for dynamic lists
- Generate IDs when data doesn't have them
Master keys and you'll avoid common React pitfalls and build more performant applications!