Key Prop in React
The key prop is a special attribute that helps React track and manage elements in a list efficiently. When rendering arrays of elements, keys enable React to determine which items have been modified, inserted, or deleted. Here's a simple example:
const users = [ { id: 1, name: 'Alice', email: 'alice@example.com' }, { id: 2, name: 'Bob', email: 'bob@example.com' }, { id: 3, name: 'Charlie', email: 'charlie@example.com' },];const userList = users.map((user) => ( <li key={user.id}> {user.name} - {user.email} </li>));When choosing a key, it's essential to select a unique identifier that distinguishes each item within its list. In real applications, you'll typically want to use unique identifiers from your data, such as database IDs. For example:
const users = [ { id: 1, name: 'Alice', email: 'alice@example.com' }, { id: 2, name: 'Bob', email: 'bob@example.com' }, { id: 3, name: 'Charlie', email: 'charlie@example.com' },];const userList = users.map((user) => ( <li key={user.id}> {user.name} - {user.email} </li>));In this example, we use user.id as the key because it's a unique identifier for each user, making it perfect for React's reconciliation process.
Why array indices as keys are a bad idea
While it might be tempting to use the array index as a key, this is generally a bad practice:
// ❌ Bad practice: using index as keyconst userList = users.map((user, index) => ( <li key={index}> {user.name} - {user.email} </li>));// ✅ Good practice: using unique ID as keyconst userList = users.map((user) => ( <li key={user.id}> {user.name} - {user.email} </li>));Using array indices as keys can lead to:
- Performance issues when the list order changes
- Bugs with component state when items are inserted or deleted
- Problems with list item animations
An index key describes a position, not an item. Take the list ["Apple", "Banana", "Cherry"] with keys 0, 1, 2. After "Apple" is deleted, the list is ["Banana", "Cherry"] with keys 0 and 1. React sees that key 0 still exists, so it keeps that component and gives it "Banana". Key 2 is gone, so React removes the last component, not the one that held "Apple".
This becomes a visible bug when list items have their own state:
function TodoList() { const [todos, setTodos] = useState([ 'Buy milk', 'Walk dog', 'Read book', ]); const removeTodo = (index) => { setTodos(todos.filter((_, i) => i !== index)); }; return ( <ul> {todos.map((todo, index) => ( <TodoItem key={index} text={todo} onRemove={() => removeTodo(index)} /> ))} </ul> );}function TodoItem({ text, onRemove }) { const [checked, setChecked] = useState(false); return ( <li> <input type="checkbox" checked={checked} onChange={() => setChecked(!checked)} /> {text} <button onClick={onRemove}>Remove</button> </li> );}If you check "Buy milk" and then remove it, the checkbox state (which lives in TodoItem) stays with key 0. Key 0 now renders "Walk dog", so "Walk dog" shows as checked even though it was never checked. Reordering the list, or inserting an item anywhere except the end, causes the same mismatch.
The only time it's acceptable to use the index as a key is when all of these conditions are met:
- The list is static (not computed and doesn't change)
- The items in the list have no IDs
- The list will never be reordered or filtered
- The items have no local state (such as form inputs or checkboxes)
The fix is a unique, stable ID. Use an ID from the data, and if the data has none, generate one when the item is created, not during render:
const newTodo = { id: crypto.randomUUID(), text: 'New todo',};Common Pitfalls
Never use Math.random() or Date.now() as a key. These change on every render, so React treats every item as new and destroys and recreates all components, which defeats the purpose of keys.