The Problem
React uses the key prop to track which items in a list have changed, been added, or been removed. When keys are duplicated, missing, or unstable, React cannot efficiently update the DOM and may render incorrect content.
Symptoms
- Console warning: "Encountered two children with the same key"
- List items display wrong content after filtering or sorting
- Input fields retain old values when items are reordered
- Animations glitch on list updates
- Checkbox state persists on wrong items after filtering
Real Error Message
Warning: Encountered two children with the same key, `item-3`. Keys
should be unique so that components maintain their identity across updates.
Non-unique keys may cause children to be duplicated and/or omitted.
at li
at TodoListCommon Causes
Cause 1: Using Array Index as Key
// BAD: Index changes meaning when items are reordered or filtered
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li> // Index 0 could be any item after filter
))}
</ul>
);
}If you filter out the first todo, the second todo now has key 0 but still contains the same DOM state (like a checked checkbox). React thinks it's the first item.
Cause 2: Non-Unique IDs from Backend
// BAD: Backend returns duplicate IDs
function UserList({ users }) {
return users.map(user => (
<div key={user.id}>{user.name}</div> // Two users share id=42
));
}Cause 3: Generated Keys from Non-Unique Data
// BAD: Multiple items can have the same category
function ProductGrid({ products }) {
return products.map(product => (
<Card key={product.category}>{product.name}</Card> // Many "Electronics"
));
}How to Fix It
Fix 1: Use Stable Unique IDs
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li> // UUID from database
))}
</ul>
);
}Fix 2: Generate Unique Keys for Backend Data Without IDs
function UserList({ users }) {
return users.map((user, index) => (
<div key={`user-${user.name}-${index}`}>{user.name}</div>
));
}Fix 3: Deduplicate Data Before Rendering
```javascript function ProductGrid({ products }) { const unique = useMemo(() => { const seen = new Set(); return products.filter(p => { if (seen.has(p.id)) return false; seen.add(p.id); return true; }); }, [products]);
return unique.map(product => ( <Card key={product.id}>{product.name}</Card> )); } ```
Fix 4: Use crypto.randomUUID for Client-Generated Items
function TodoApp() {
const addTodo = (text) => {
const newTodo = {
id: crypto.randomUUID(), // Guaranteed unique
text,
completed: false
};
setTodos(prev => [...prev, newTodo]);
};
}Prevention
- Never use array index as key when items can be reordered, filtered, or deleted
- Always use database-generated IDs or UUIDs
- Run
eslint-plugin-reactwith thereact/jsx-keyrule enabled - Test list components with filter, sort, and reorder scenarios