A common React pitfall is not updating key
props when your component’s underlying data changes, which can lead to subtle bugs and performance issues.
The Problem
When state changes but keys remain the same for different data, React’s reconciliation algorithm may:
- Keep incorrect DOM elements mounted
- Preserve stale component state
- Fail to trigger proper lifecycle methods
- Cause performance degradation
Common Scenarios
1. List Items with Changing Data
// 🚫 Problematic - same keys for different todos after refresh
function TodoList() {
const [todos, setTodos] = useState([]);
const refreshTodos = async () => {
const newTodos = await fetchTodos(); // Returns completely new array
setTodos(newTodos);
// Same keys but potentially different todo objects
};
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
2. Component State Reset Needed
// 🚫 Internal state won't reset when user changes
function UserProfile({ userId }) {
const [expanded, setExpanded] = useState(false);
return (
<div>
<button onClick={() => setExpanded(!expanded)}>
Toggle
</button>
{expanded && <UserDetails userId={userId} />}
</div>
);
}
Solutions
1. Proper Key Strategy for Lists
// ✅ Good - keys will force proper re-renders
{todos.map(todo => (
<li key={`${todo.id}-${todo.updatedAt}`}>
{todo.text}
</li>
))}
2. Key-Based Component Reset
// ✅ Changing key forces full remount when userId changes
<UserDetails key={userId} userId={userId} />
3. Composite Keys for Complex State
// ✅ When multiple factors affect component identity
<DataVisualization
key={`${dataset.id}-${viewMode}-${timeRange}`}
dataset={dataset}
viewMode={viewMode}
timeRange={timeRange}
/>
When to Update Keys
Update component keys when:
- The underlying data identity changes
- You need to force a component reset
- External state changes should clear internal state
- You switch between fundamentally different views
Performance Considerations
While changing keys forces remounts:
- This is sometimes exactly what you want
- The performance cost is often worth the correctness guarantee
- For heavy components, consider memoization instead
Advanced Pattern: Key Factory Functions
function getItemKey(item) {
return `${item.id}_${item.version}_${item.updatedAt}`;
}
// Usage
{items.map(item => (
<ItemComponent key={getItemKey(item)} item={item} />
))}