React Tutorial 0/120 lessons ~6 min read Lesson 23

    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.

    Course progress0%
    Focus
    33 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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:

    1. What lists are in React and how map() works
    2. Why React needs keys
    3. What makes a good key (unique, stable, identity-based)
    4. Why index keys and random keys are risky
    5. Rendering components in lists (and that key is not a prop)
    6. Filter / sort before map
    7. Empty, loading, and error states for lists
    8. Nested lists and keyed Fragments
    9. 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.

    tsx
    const fruits = ["Apple", "Banana", "Mango"];

    We could display them individually:

    tsx
    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():

    tsx
    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.

    text
    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:

    tsx
    array.map((item) => (
    <Element>{item}</Element>
    ))

    Example:

    tsx
    const users = ["Rahul", "Priya", "Amit"];
    function Users() {
    return (
    <ul>
    {users.map((user) => (
    <li>{user}</li>
    ))}
    </ul>
    );
    }

    This produces:

    text
    Rahul
    Priya
    Amit

    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?

    tsx
    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:

    text
    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:

    tsx
    {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.

    tsx
    <li key={user.id}>
    {user.name}
    </li>
    text
    User ID → React Key
    1 → 1
    2 → 2
    3 → 3

    Suppose the original list is:

    text
    1 Rahul
    2 Priya
    3 Amit

    Then we add another user:

    text
    1 Rahul
    2 Priya
    3 Amit
    4 Sneha

    React can recognize:

    text
    1 → same item
    2 → same item
    3 → same item
    4 → 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:

    tsx
    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:

    tsx
    const users = [
    { id: 101, name: "Rahul" },
    { id: 101, name: "Priya" }
    ];
    text
    101 → Rahul
    101 → 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:

    tsx
    {users.map((user) => (
    <li key={user.id}>
    {user.name}
    </li>
    ))}

    Not:

    tsx
    {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.

    tsx
    function UserCard({ user }) {
    return (
    <div>
    <h3>{user.name}</h3>
    <p>{user.email}</p>
    </div>
    );
    }
    function UserList({ users }) {
    return (
    <div>
    {users.map((user) => (
    <UserCard
    key={user.id}
    user={user}
    />
    ))}
    </div>
    );
    }

    Notice that key is not automatically available inside UserCard.

    8. key Is Not a Normal Prop

    tsx
    <UserCard
    key={user.id}
    user={user}
    />

    You cannot access the key like this:

    tsx
    function UserCard(props) {
    console.log(props.key); // undefined — key is not a prop
    }

    If the component needs the ID, pass it explicitly:

    tsx
    <UserCard
    key={user.id}
    id={user.id}
    user={user}
    />
    function UserCard({ id, user }) {
    console.log(id);
    return (
    <div>
    {user.name}
    </div>
    );
    }
    text
    key → React's identity information
    id → application data

    9. Using Array Index as a Key

    A common beginner pattern:

    tsx
    {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:

    text
    Index 0 → Rahul
    Index 1 → Priya
    Index 2 → Amit

    Remove Rahul:

    text
    Index 0 → Priya
    Index 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:

    tsx
    function UserList() {
    const [users, setUsers] = useState([
    { id: 1, name: "Rahul" },
    { id: 2, name: "Priya" },
    { id: 3, name: "Amit" }
    ]);
    return (
    <div>
    {users.map((user, index) => (
    <UserInput
    key={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:

    text
    Before removal:
    Component A → Rahul
    Component B → Priya
    Component C → Amit
    After removing Rahul:
    Component A → Priya
    Component 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.

    tsx
    const products = [
    { id: "p101", name: "Laptop" },
    { id: "p102", name: "Phone" },
    { id: "p103", name: "Headphones" }
    ];
    {products.map((product) => (
    <ProductCard
    key={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.

    tsx
    key={user.id}

    Stable

    The key should not change when the item moves.

    tsx
    key={user.id}

    Predictable

    The key should represent the identity of the data.

    tsx
    key={product.id}

    Avoid generating a new random key on every render:

    tsx
    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:

    text
    A
    B
    C

    After reordering:

    text
    C
    A
    B

    With stable keys, React recognizes the same items in new positions:

    text
    A → key A
    B → key B
    C → key C

    With index keys:

    text
    Before:
    0 → A
    1 → B
    2 → C
    After:
    0 → C
    1 → A
    2 → 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.

    tsx
    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>
    );
    }
    text
    Data
    ↓
    Array
    ↓
    map()
    ↓
    React element
    ↓
    key + UI

    15. Combining Lists with Components

    Separate the list item into its own component:

    tsx
    function ProductCard({ product }) {
    return (
    <article>
    <h3>{product.name}</h3>
    <p>₹{product.price}</p>
    </article>
    );
    }
    function ProductList({ products }) {
    return (
    <section>
    {products.map((product) => (
    <ProductCard
    key={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.

    tsx
    {products
    .filter((product) => product.price < 50000)
    .map((product) => (
    <ProductCard
    key={product.id}
    product={product}
    />
    ))}
    text
    All Products
    ↓
    filter()
    ↓
    Matching Products
    ↓
    map()
    ↓
    UI

    This is often cleaner than placing complicated conditions inside JSX.

    17. Sorting Before Mapping

    tsx
    {products
    .slice()
    .sort((a, b) => a.price - b.price)
    .map((product) => (
    <ProductCard
    key={product.id}
    product={product}
    />
    ))}

    .slice() creates a copy so the original array is not mutated by sort().

    text
    data
    ↓
    transform
    ↓
    filter
    ↓
    sort
    ↓
    map
    ↓
    UI

    18. Rendering Lists Conditionally

    Lists often need an empty state. Prefer an explicit empty message:

    tsx
    {products.length === 0 ? (
    <p>No products found.</p>
    ) : (
    products.map((product) => (
    <ProductCard
    key={product.id}
    product={product}
    />
    ))
    )}

    19. Empty, Loading, and Error States

    text
    Loading
    ↓
    Fetching data
    ↓
    ┌───────────────┐
    │ │
    Success Error
    │ │
    ▼ ▼
    List Error UI
    │
    ▼
    Empty?
    │
    ├── Yes → Empty State
    │
    └── No → Display List
    tsx
    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) => (
    <ProductCard
    key={product.id}
    product={product}
    />
    ))}
    </div>
    );

    20. Nested Lists

    tsx
    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:

    tsx
    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.

    text
    Index = position
    Key = identity

    For dynamic lists, identity is usually what React needs.

    23. Practical Example: Todo List

    tsx
    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) => (
    <li
    key={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

    tsx
    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) => (
    <ProductCard
    key={product.id}
    product={product}
    />
    ))}
    </section>
    );
    }
    text
    ProductList
    │
    ├── ProductCard
    ├── ProductCard
    ├── ProductCard
    └── ProductCard

    25. Common Mistakes

    Mistake 1: Forgetting the key

    tsx
    {users.map((user) => (
    <li>{user.name}</li>
    ))}

    Better:

    tsx
    {users.map((user) => (
    <li key={user.id}>
    {user.name}
    </li>
    ))}

    Mistake 2: Using the array index unnecessarily

    tsx
    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

    tsx
    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>:

    tsx
    {users.map((user) => (
    <li>
    <UserCard
    key={user.id}
    user={user}
    />
    </li>
    ))}

    Correct:

    tsx
    {users.map((user) => (
    <li key={user.id}>
    <UserCard user={user} />
    </li>
    ))}

    26. Best Practices

    1. Prefer stable IDs: key={item.id}
    2. Keep keys unique among siblings
    3. Avoid array indexes for dynamic lists (indexes only when the list is truly static)
    4. Do not generate random keys (Math.random())
    5. Put keys on the list's direct children (the element returned by map())
    6. Keep transformation logic readable — filter/sort into a variable when complex, then map
    7. Separate list items into components when appropriate (ProductCard, UserCard, TodoItem)

    27. Lists + Keys Mental Model

    text
    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:

    text
    A → removed
    B → existing
    C → existing
    D → 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:

    tsx
    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:

    tsx
    <UserCard
    key={user.id}
    userId={user.id}
    />

    7. Can a Fragment have a key?

    Yes, with explicit syntax:

    tsx
    <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:

    tsx
    const languages = [
    "JavaScript",
    "TypeScript",
    "Python",
    "Java"
    ];

    Exercise 2 — Objects and Keys

    tsx
    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

    tsx
    {users.map((user, index) => (
    <UserCard
    key={index}
    user={user}
    />
    ))}

    Explain when this could cause problems (reorder, insert/remove, stateful child components).

    30. Quick Cheat Sheet

    ConceptExample
    Render listitems.map(...)
    Basic keykey={item.id}
    Access index(item, index)
    Filter listitems.filter(...)
    Sort listitems.slice().sort(...)
    Component list<Card key={item.id} />
    Empty listitems.length === 0
    Fragment key<Fragment key={id}>
    Avoidkey={Math.random()}
    Usually avoidkey={index}

    Key Takeaway

    Lists allow React to turn collections of JavaScript data into dynamic UI.

    The fundamental pattern is:

    tsx
    {items.map((item) => (
    <Component
    key={item.id}
    item={item}
    />
    ))}

    Remember the three most important rules:

    text
    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.

    Ready to mark this lesson complete?Track your journey across the entire course.