Difficulty: Beginner
Why are keys important in React lists? What happens when you use wrong keys?
Keys in React lists. This might seem like a basic topic, but I've seen senior developers get this wrong, and the bugs it causes are some of the most confusing to debug. Let me explain what's really going on under the hood.
First, let's understand why keys exist at all. When React re-renders a component that returns a list of elements, it needs to figure out what changed. Did an item get added? Removed? Reordered? Updated? React's reconciliation algorithm (the diffing process) compares the old tree with the new tree to determine the minimum number of DOM operations needed. But when looking at a list of siblings, React can't just compare them by position, because items might have moved around.
This is where keys come in. Keys are like name tags at a conference. Without name tags, if everyone shuffled their seats, you'd think every person changed (because the person at seat 1 is different now). With name tags, you can immediately tell: 'Oh, Alice moved from seat 1 to seat 3, and Bob is new.' You'd only need to move Alice and add a chair for Bob, rather than replacing everyone.
Here's what React actually does. When you render a list, React builds a map of key -> element. On the next render, it builds a new map and compares the two. Same key, same type? Keep the DOM node and update props if needed. New key? Create a new DOM node. Key disappeared? Remove the DOM node. Key at a different position? Move the DOM node. This is incredibly efficient compared to destroying and recreating everything.
Now, the big question: why is using array index as a key a problem? Let me show you a concrete scenario that will make it click.
Say you have a list of three todos: [{id: 'a', text: 'Buy milk'}, {id: 'b', text: 'Walk dog'}, {id: 'c', text: 'Code'}]. If you render them with key={index}, the first todo gets key=0, second gets key=1, third gets key=2. Now the user checks the checkbox on 'Buy milk' (key=0) and then adds a new todo at the beginning of the list.
The new list is: [{id: 'd', text: 'New task'}, {id: 'a', text: 'Buy milk'}, {id: 'b', text: 'Walk dog'}, {id: 'c', text: 'Code'}]. With index keys, 'New task' gets key=0, 'Buy milk' gets key=1, etc. React sees: 'key=0 was Buy milk, now it's New task. The content changed, so update the text. But the checkbox state stays where it is because the component instance at key=0 is reused.' Result: the 'New task' item shows up with a checked checkbox, and 'Buy milk' is now unchecked. The state got mixed up between items.
With unique ID keys (key={todo.id}), React sees: 'key=d is new, add it. key=a moved from position 0 to position 1, move it with all its state intact. key=b and key=c also moved.' Result: the checkbox stays with 'Buy milk' where it belongs. Everything works correctly.
This is not just a theoretical problem. In production, this manifests as form inputs showing the wrong values, animations playing on the wrong elements, focus jumping to unexpected inputs, and all sorts of subtle visual glitches that are incredibly hard to track down because the symptom (wrong state on wrong item) is so far removed from the cause (bad keys).
So what makes a good key?
- Database IDs are perfect: user.id, todo.id, product.sku - Business-unique values work too: user.email, product.slug - UUIDs generated at item creation time (NOT at render time) are fine - Any string that uniquely identifies the item and stays the same across renders
What makes a bad key?
- Array index (unless the list is completely static and never reordered, filtered, or added to) - Math.random() or Date.now() called during render. This generates a new key every single render, which tells React that every item was deleted and new ones were added. React unmounts and remounts everything, destroying all state and DOM nodes. This is terrible for performance and causes visible flashing. - Non-unique values. If two items have the same key, React will warn you and behave unpredictably.
There's one more powerful pattern to know: using key as a reset mechanism. When you change a component's key, React treats it as a completely different component instance. It unmounts the old one (destroying all state) and mounts a fresh one. This is incredibly useful when you need to reset a form when switching between editing different items. Instead of writing a useEffect that manually resets every piece of state when the ID changes, you just put key={userId} on the form component. When userId changes, React automatically unmounts the old form and mounts a fresh one with clean state. It's cleaner, less error-prone, and handles edge cases (like async effects from the previous ID still running) automatically.
Some final details: keys only need to be unique among siblings, not globally unique. You can use the same key in different lists without any issue. Also, the key prop is consumed by React internally. It's not passed to the component as a prop. If your component needs the ID for rendering or API calls, pass it as a separate prop like itemId={item.id} in addition to key={item.id}.
In interviews, walk through the adding-to-beginning example with index keys. It's visual, concrete, and immediately demonstrates why stable unique keys matter.
function TodoList() {
const [todos, setTodos] = useState([
{ id: 'a', text: 'First' },
{ id: 'b', text: 'Second' },
{ id: 'c', text: 'Third' },
]);
function addToStart() {
setTodos(prev => [
{ id: Date.now().toString(), text: 'New item' },
...prev
]);
}
return (
<div>
<button onClick={addToStart}>Add to Start</button>
{/* BAD: index key - checkbox state breaks on reorder */}
{todos.map((todo, index) => (
<TodoItem key={index} todo={todo} />
))}
{/* GOOD: unique id key - state follows the item */}
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</div>
);
}
function TodoItem({ todo }) {
const [checked, setChecked] = useState(false);
return (
<label>
<input type="checkbox" checked={checked}
onChange={() => setChecked(!checked)} />
{todo.text}
</label>
);
}
With index keys, adding an item to the start shifts all indices. The checkbox state for 'First' (index 0) now appears on 'New item' (new index 0). Unique ID keys keep state attached to the correct item.
function EditProfile({ userId }) {
// When userId changes, we want to reset the form
// Option 1: useEffect to reset state (messy)
// const [name, setName] = useState('');
// useEffect(() => setName(''), [userId]);
// Option 2: Use key to force remount (clean!)
return <ProfileForm key={userId} userId={userId} />;
}
function ProfileForm({ userId }) {
const [name, setName] = useState('');
const [bio, setBio] = useState('');
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(user => {
setName(user.name);
setBio(user.bio);
});
}, [userId]);
return (
<form>
<input value={name} onChange={e => setName(e.target.value)} />
<textarea value={bio} onChange={e => setBio(e.target.value)} />
</form>
);
}
// Changing key unmounts old component and mounts fresh one
// All state is reset automatically - no useEffect needed
Changing a component's key tells React to destroy the old instance and create a new one. This is a clean way to reset all state without manually tracking what needs to be cleared.
// Scenario: Move item from end to beginning
// Items: [A, B, C, D, E] -> [E, A, B, C, D]
// With index keys: React sees
// key=0: A -> E (update content)
// key=1: B -> A (update content)
// key=2: C -> B (update content)
// key=3: D -> C (update content)
// key=4: E -> D (update content)
// Result: 5 DOM updates!
// With unique keys: React sees
// key='e': moved from position 4 to 0
// key='a': moved from position 0 to 1
// key='b': stayed same content
// key='c': stayed same content
// key='d': stayed same content
// Result: 1 DOM move operation!
// When index keys are acceptable:
// 1. Static lists that never change order
// 2. No reordering, filtering, or insertion at beginning
// 3. Items have no internal state
function StaticList() {
const menuItems = ['Home', 'About', 'Contact'];
return (
<nav>
{menuItems.map((item, index) => (
<a key={index} href={`/${item.toLowerCase()}`}>{item}</a>
))}
</nav>
);
}
Unique keys let React identify moved items and perform minimal DOM operations. Index keys force React to update every item when the order changes.
Keys, List Rendering, Reconciliation, Performance