Lists & Keys
Lists and keys in React: render collections with map(), choose stable unique keys, avoid index/random keys, and handle empty, loading, and nested lists.
Lists & Keys
Lists & Keys are one of the foundational pieces of modern React engineering.
Almost every real-world React application needs to display collections of data:
- Products
- Users
- Orders
- Messages
- Comments
- Notifications
- Navigation links
- Search results
- Table rows
- Blog posts
React makes rendering these collections straightforward using JavaScript array methods, especially map().
However, rendering a list is only half of the story. React also needs a way to identify each item in the list. This is where keys come in.
Incorrect key usage can cause:
- Unexpected UI behavior
- Incorrect component state
- Unnecessary re-renders
- Difficult-to-debug bugs
- Poor performance
Learning Objectives
By the end of this lesson, you will understand:
- What lists are in React and how
map()works - Why React needs keys
- What makes a good key (unique, stable, identity-based)
- Why index keys and random keys are risky
- Rendering components in lists (and that
keyis not a prop) - Filter / sort before map
- Empty, loading, and error states for lists
- Nested lists and keyed Fragments
- Common mistakes, best practices, and interview questions
1. What Are Lists in React?
A list is simply a collection of similar elements that we want to display.
const fruits = ["Apple", "Banana", "Mango"];
We could display them individually:
function FruitList() {return (<div><p>Apple</p><p>Banana</p><p>Mango</p></div>);}
But this becomes impractical when the data is dynamic. Instead, use map():
function FruitList() {const fruits = ["Apple", "Banana", "Mango"];return (<div>{fruits.map((fruit) => (<p>{fruit}</p>))}</div>);}
The map() method creates a React element for every item in the array.
Array│├── Apple├── Banana└── Mango│▼map()│▼React Elements│├── <p>Apple</p>├── <p>Banana</p>└── <p>Mango</p>
2. Rendering Arrays with map()
The most common pattern for rendering lists is:
array.map((item) => (<Element>{item}</Element>))
Example:
const users = ["Rahul", "Priya", "Amit"];function Users() {return (<ul>{users.map((user) => (<li>{user}</li>))}</ul>);}
This produces:
RahulPriyaAmit
map() is a JavaScript feature. React does not provide a special list-loop syntax — you use normal JavaScript to transform data into UI.
3. Why Does React Need Keys?
const users = [{ id: 1, name: "Rahul" },{ id: 2, name: "Priya" },{ id: 3, name: "Amit" }];function UserList() {return (<ul>{users.map((user) => (<li>{user.name}</li>))}</ul>);}
React will display the list, but the console will warn:
Each child in a list should have a unique "key" prop.
React needs a way to determine which item corresponds to which rendered element. Provide that identity with the key prop:
{users.map((user) => (<li key={user.id}>{user.name}</li>))}
Now each item has a stable identity.
4. What Is a Key?
A key is a special React attribute that identifies an element among its siblings.
<li key={user.id}>{user.name}</li>
User ID → React Key1 → 12 → 23 → 3
Suppose the original list is:
1 Rahul2 Priya3 Amit
Then we add another user:
1 Rahul2 Priya3 Amit4 Sneha
React can recognize:
1 → same item2 → same item3 → same item4 → new item
This allows React to update the UI intelligently.
5. Keys Must Be Unique Among Siblings
Keys should be unique within the list being rendered.
Good:
const users = [{ id: 101, name: "Rahul" },{ id: 102, name: "Priya" },{ id: 103, name: "Amit" }];users.map((user) => (<li key={user.id}>{user.name}</li>));
Bad — two siblings share the same key:
const users = [{ id: 101, name: "Rahul" },{ id: 101, name: "Priya" }];
101 → Rahul101 → Priya
Keys must also be stable. A key should represent the identity of the item, not its current position.
6. Where Should the Key Go?
Place the key on the outermost element returned by the map() callback.
Correct:
{users.map((user) => (<li key={user.id}>{user.name}</li>))}
Not:
{users.map((user) => (<li><span key={user.id}>{user.name}</span></li>))}
The <li> elements are the actual siblings in the rendered list, so the key belongs on <li>.
7. Rendering Components in a List
Lists are not limited to HTML elements — you can render React components.
function UserCard({ user }) {return (<div><h3>{user.name}</h3><p>{user.email}</p></div>);}function UserList({ users }) {return (<div>{users.map((user) => (<UserCardkey={user.id}user={user}/>))}</div>);}
Notice that key is not automatically available inside UserCard.
8. key Is Not a Normal Prop
<UserCardkey={user.id}user={user}/>
You cannot access the key like this:
function UserCard(props) {console.log(props.key); // undefined — key is not a prop}
If the component needs the ID, pass it explicitly:
<UserCardkey={user.id}id={user.id}user={user}/>function UserCard({ id, user }) {console.log(id);return (<div>{user.name}</div>);}
key → React's identity informationid → application data
9. Using Array Index as a Key
A common beginner pattern:
{users.map((user, index) => (<li key={index}>{user.name}</li>))}
This works technically, but it is often a poor choice — an array index represents position, not identity.
Initial list:
Index 0 → RahulIndex 1 → PriyaIndex 2 → Amit
Remove Rahul:
Index 0 → PriyaIndex 1 → Amit
The indexes changed. React may interpret the same positions as representing different data — especially problematic when list items contain state.
10. Why Index Keys Can Cause Bugs
Consider a list of editable inputs, each with its own internal state:
function UserList() {const [users, setUsers] = useState([{ id: 1, name: "Rahul" },{ id: 2, name: "Priya" },{ id: 3, name: "Amit" }]);return (<div>{users.map((user, index) => (<UserInputkey={index}user={user}/>))}</div>);}
If the first user is removed, React sees key 0 and key 1 still exist, so it may preserve component state based on those keys:
Before removal:Component A → RahulComponent B → PriyaComponent C → AmitAfter removing Rahul:Component A → PriyaComponent B → Amit
Component instances were reused based on position. Stable keys prevent this.
11. Prefer Stable IDs
If your data contains an ID, use it.
const products = [{ id: "p101", name: "Laptop" },{ id: "p102", name: "Phone" },{ id: "p103", name: "Headphones" }];{products.map((product) => (<ProductCardkey={product.id}product={product}/>))}
This is much better than key={index}.
12. What Makes a Good Key?
A good key should be:
Unique
Different siblings should have different keys.
key={user.id}
Stable
The key should not change when the item moves.
key={user.id}
Predictable
The key should represent the identity of the data.
key={product.id}
Avoid generating a new random key on every render:
key={Math.random()} // bad — new key every render
Every render generates different keys, so React may treat existing elements as entirely new ones.
13. Keys and Reordering
Keys become especially important when items move.
Before:
ABC
After reordering:
CAB
With stable keys, React recognizes the same items in new positions:
A → key AB → key BC → key C
With index keys:
Before:0 → A1 → B2 → CAfter:0 → C1 → A2 → B
The keys remain 0, 1, 2, but now refer to different items — causing unnecessary reuse and state bugs.
14. Lists with Objects
Real applications usually work with objects, not arrays of strings.
const products = [{ id: 1, name: "MacBook", price: 120000 },{ id: 2, name: "iPhone", price: 80000 },{ id: 3, name: "AirPods", price: 20000 }];function ProductList() {return (<div>{products.map((product) => (<div key={product.id}><h3>{product.name}</h3><p>₹{product.price}</p></div>))}</div>);}
Data↓Array↓map()↓React element↓key + UI
15. Combining Lists with Components
Separate the list item into its own component:
function ProductCard({ product }) {return (<article><h3>{product.name}</h3><p>₹{product.price}</p></article>);}function ProductList({ products }) {return (<section>{products.map((product) => (<ProductCardkey={product.id}product={product}/>))}</section>);}
This improves reusability, readability, maintainability, testing, and separation of concerns.
16. Filtering Before Mapping
A very common pattern is filter → map.
{products.filter((product) => product.price < 50000).map((product) => (<ProductCardkey={product.id}product={product}/>))}
All Products↓filter()↓Matching Products↓map()↓UI
This is often cleaner than placing complicated conditions inside JSX.
17. Sorting Before Mapping
{products.slice().sort((a, b) => a.price - b.price).map((product) => (<ProductCardkey={product.id}product={product}/>))}
.slice() creates a copy so the original array is not mutated by sort().
data↓transform↓filter↓sort↓map↓UI
18. Rendering Lists Conditionally
Lists often need an empty state. Prefer an explicit empty message:
{products.length === 0 ? (<p>No products found.</p>) : (products.map((product) => (<ProductCardkey={product.id}product={product}/>)))}
19. Empty, Loading, and Error States
Loading↓Fetching data↓┌───────────────┐│ │Success Error│ │▼ ▼List Error UI│▼Empty?│├── Yes → Empty State│└── No → Display List
if (loading) {return <p>Loading products...</p>;}if (error) {return <p>Unable to load products.</p>;}if (products.length === 0) {return <p>No products available.</p>;}return (<div>{products.map((product) => (<ProductCardkey={product.id}product={product}/>))}</div>);
20. Nested Lists
const categories = [{id: 1,name: "Electronics",products: [{ id: 101, name: "Laptop" },{ id: 102, name: "Phone" }]},{id: 2,name: "Books",products: [{ id: 201, name: "React Guide" },{ id: 202, name: "JavaScript Guide" }]}];function Categories() {return (<div>{categories.map((category) => (<section key={category.id}><h2>{category.name}</h2><ul>{category.products.map((product) => (<li key={product.id}>{product.name}</li>))}</ul></section>))}</div>);}
Each list has its own key scope. Category keys identify category siblings; product keys identify product siblings.
21. Fragment Lists
Sometimes a list item needs to return multiple elements. The shorthand <> cannot receive a key — use explicit Fragment:
import { Fragment } from "react";{users.map((user) => (<Fragment key={user.id}><h3>{user.name}</h3><p>{user.email}</p></Fragment>))}
22. Keys Are About Identity, Not Performance Alone
A common misconception is that keys only make React faster. Performance matters, but identity is more important.
Index = positionKey = identity
For dynamic lists, identity is usually what React needs.
23. Practical Example: Todo List
const todos = [{ id: 1, title: "Learn React", completed: true },{ id: 2, title: "Build a project", completed: false },{ id: 3, title: "Practice JavaScript", completed: false }];function TodoList() {return (<ul>{todos.map((todo) => (<likey={todo.id}className={todo.completed ? "completed" : ""}>{todo.title}</li>))}</ul>);}
This combines array data, map(), keys, conditional rendering, and dynamic attributes — patterns you will use constantly.
24. Practical Example: Product Catalog
function ProductCard({ product }) {return (<article className="product-card"><h3>{product.name}</h3><p>₹{product.price}</p><button>Add to Cart</button></article>);}function ProductList({ products }) {if (products.length === 0) {return <p>No products available.</p>;}return (<section className="product-grid">{products.map((product) => (<ProductCardkey={product.id}product={product}/>))}</section>);}
ProductList│├── ProductCard├── ProductCard├── ProductCard└── ProductCard
25. Common Mistakes
Mistake 1: Forgetting the key
{users.map((user) => (<li>{user.name}</li>))}
Better:
{users.map((user) => (<li key={user.id}>{user.name}</li>))}
Mistake 2: Using the array index unnecessarily
key={index}
Avoid when the list can be reordered, have items inserted/removed, be filtered, or contain stateful components. Prefer key={item.id}.
Mistake 3: Generating random keys
key={Math.random()}
Keys should remain stable between renders.
Mistake 4: Using a non-unique value
Avoid key={user.name} if names can collide. Use a unique ID.
Mistake 5: Putting the key on the wrong element
Incorrect — list children are <li>:
{users.map((user) => (<li><UserCardkey={user.id}user={user}/></li>))}
Correct:
{users.map((user) => (<li key={user.id}><UserCard user={user} /></li>))}
26. Best Practices
- Prefer stable IDs:
key={item.id} - Keep keys unique among siblings
- Avoid array indexes for dynamic lists (indexes only when the list is truly static)
- Do not generate random keys (
Math.random()) - Put keys on the list's direct children (the element returned by
map()) - Keep transformation logic readable — filter/sort into a variable when complex, then
map - Separate list items into components when appropriate (
ProductCard,UserCard,TodoItem)
27. Lists + Keys Mental Model
DATA│▼Array│▼map()│├── Item A → key="a"├── Item B → key="b"└── Item C → key="c"│▼React Elements│▼UI
When the data changes from A B C to B C D, stable keys tell React:
A → removedB → existingC → existingD → new
That is the core purpose of keys.
28. Interview Questions
1. How do you render a list in React?
Use JavaScript's map() method:
items.map((item) => (<li key={item.id}>{item.name}</li>))
2. Why are keys required in React lists?
Keys allow React to identify list items across renders and correctly reconcile changes.
3. What makes a good key?
- Unique among siblings
- Stable
- Based on item identity
4. Why should you avoid array indexes as keys?
Indexes represent positions rather than stable item identities. When items are inserted, removed, or reordered, index keys can associate incorrect component state with different items.
5. Can two separate lists use the same key?
Yes. Keys only need to be unique among their sibling elements.
6. Is key available as a component prop?
No. key is a special React attribute. Pass the ID separately if needed:
<UserCardkey={user.id}userId={user.id}/>
7. Can a Fragment have a key?
Yes, with explicit syntax:
<Fragment key={item.id}>...</Fragment>
The shorthand <> cannot receive a key.
8. Is using an index as a key always wrong?
No. It can be acceptable when the list is static, never reordered, and items are never inserted or removed. Stable IDs are generally the safer choice.
29. Practice Exercises
Exercise 1 — Basic List
Create an array of languages and render it as an unordered list:
const languages = ["JavaScript","TypeScript","Python","Java"];
Exercise 2 — Objects and Keys
const users = [{ id: 1, name: "Rahul" },{ id: 2, name: "Priya" },{ id: 3, name: "Amit" }];
Render the names and use each user's ID as the key.
Exercise 3 — Product Cards
Create products with ID, name, price, and category. Render each with a reusable ProductCard.
Exercise 4 — Filtering
Display only products priced below ₹50,000 using filter() + map().
Exercise 5 — Empty State
When there are no products, show: No products available.
Exercise 6 — Identify the Problem
{users.map((user, index) => (<UserCardkey={index}user={user}/>))}
Explain when this could cause problems (reorder, insert/remove, stateful child components).
30. Quick Cheat Sheet
| Concept | Example |
|---|---|
| Render list | items.map(...) |
| Basic key | key={item.id} |
| Access index | (item, index) |
| Filter list | items.filter(...) |
| Sort list | items.slice().sort(...) |
| Component list | <Card key={item.id} /> |
| Empty list | items.length === 0 |
| Fragment key | <Fragment key={id}> |
| Avoid | key={Math.random()} |
| Usually avoid | key={index} |
Key Takeaway
Lists allow React to turn collections of JavaScript data into dynamic UI.
The fundamental pattern is:
{items.map((item) => (<Componentkey={item.id}item={item}/>))}
Remember the three most important rules:
1. Use map() to render collections.2. Give every list item a stable, unique key.3. Prefer item identity (id) over array position (index).
The most important mental model: A key identifies an item; an index identifies a position.
Once you understand that distinction, React list rendering becomes much easier to reason about — and you'll avoid one of the most common sources of subtle React bugs.