> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 05. Lists & Keys

> Master rendering lists, understanding keys, and implementing advanced list patterns.

# Lists & Keys

Rendering lists of data is one of the most common tasks in React. Understanding how to do it correctly and efficiently is crucial for building performant applications.

**Real-world analogy for keys**: Imagine a teacher taking attendance in a classroom. If students do not have names (no keys), and one student leaves, the teacher has to re-check every seat from the beginning. But if every student has a name badge (a key), the teacher instantly knows which student left and which ones just shifted seats. That is exactly what React's `key` prop does -- it gives each list item an identity so React can efficiently track additions, removals, and reorderings without rebuilding the entire list.

## Rendering Lists with map()

Use JavaScript's `map()` method to transform an array of data into an array of elements.

```javascript theme={null}
function SimpleList() {
  const fruits = ['Apple', 'Banana', 'Cherry', 'Date'];

  return (
    <ul>
      {fruits.map((fruit, index) => (
        <li key={index}>{fruit}</li>
      ))}
    </ul>
  );
}
```

### Rendering Objects

```javascript theme={null}
function UserList() {
  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' }
  ];

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          <strong>{user.name}</strong> - {user.email}
        </li>
      ))}
    </ul>
  );
}
```

***

## Understanding Keys

Keys are special string attributes you need to include when creating lists of elements. They help React identify which items have changed, been added, or been removed.

### Why Keys Matter

React uses keys during its **reconciliation** process to efficiently update the DOM:

```
Without Keys:
┌──────────────────────────────────────────────────────────┐
│  Old List        Change         New List                │
│  ┌─────────┐                   ┌─────────┐              │
│  │ Item A  │    Insert X      │ Item X  │ ← Recreated  │
│  ├─────────┤    at start      ├─────────┤              │
│  │ Item B  │                  │ Item A  │ ← Recreated  │
│  ├─────────┤                  ├─────────┤              │
│  │ Item C  │                  │ Item B  │ ← Recreated  │
│  └─────────┘                  ├─────────┤              │
│                               │ Item C  │ ← Recreated  │
│                               └─────────┘              │
│  React rebuilds EVERYTHING (O(n) operations)           │
└──────────────────────────────────────────────────────────┘

With Keys:
┌──────────────────────────────────────────────────────────┐
│  Old List        Change         New List                │
│  ┌─────────┐                   ┌─────────┐              │
│  │ key="a" │    Insert X      │ key="x" │ ← NEW        │
│  ├─────────┤    at start      ├─────────┤              │
│  │ key="b" │                  │ key="a" │ ← Moved      │
│  ├─────────┤                  ├─────────┤              │
│  │ key="c" │                  │ key="b" │ ← Moved      │
│  └─────────┘                  ├─────────┤              │
│                               │ key="c" │ ← Moved      │
│                               └─────────┘              │
│  React only INSERTS the new item (O(1) operation)      │
└──────────────────────────────────────────────────────────┘
```

### Key Rules

1. **Keys must be unique among siblings** (not globally unique)
2. **Keys should be stable** (same item = same key across renders)
3. **Keys should not change** (don't use random values)

### What to Use as Keys

```javascript theme={null}
// ✅ BEST: Database IDs
{users.map(user => <UserCard key={user.id} user={user} />)}

// ✅ GOOD: Unique identifiers in data
{products.map(product => <ProductCard key={product.sku} product={product} />)}

// ✅ OK: Generated unique IDs (when adding items)
const [todos, setTodos] = useState([]);
const addTodo = (text) => {
  setTodos([...todos, { id: crypto.randomUUID(), text }]);
};

// ⚠️ CAUTION: Index (only for static lists that never reorder)
{staticItems.map((item, index) => <li key={index}>{item}</li>)}

// ❌ BAD: Random values
{items.map(item => <li key={Math.random()}>{item}</li>)}
```

<Warning>
  **Never use `Math.random()` or `Date.now()` as keys!** These change on every render, causing React to recreate all elements, destroying component state and causing performance issues.
</Warning>

### The Index Key Problem

Using array index as key causes one of the most frustrating bugs in React: **state gets associated with the wrong item** when the list order changes. This happens because React uses the key to match old and new elements. If the key stays the same but the item at that position changes, React reuses the old component instance (including its internal state) for a completely different item.

```javascript theme={null}
// ❌ PROBLEMATIC - State gets mixed up when reordering
function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React' },
    { id: 2, text: 'Build project' }
  ]);

  const moveToTop = (id) => {
    const todo = todos.find(t => t.id === id);
    setTodos([todo, ...todos.filter(t => t.id !== id)]);
  };

  return (
    <ul>
      {todos.map((todo, index) => (
        // Using index as key - BAD when reordering!
        <TodoItem key={index} todo={todo} />
      ))}
    </ul>
  );
}

// ✅ CORRECT - Use unique ID
{todos.map(todo => <TodoItem key={todo.id} todo={todo} />)}
```

***

## Filtering Lists

```javascript theme={null}
function FilterableProductList() {
  const [filter, setFilter] = useState('all');
  
  const products = [
    { id: 1, name: 'iPhone', category: 'electronics', price: 999 },
    { id: 2, name: 'T-Shirt', category: 'clothing', price: 29 },
    { id: 3, name: 'MacBook', category: 'electronics', price: 1999 },
    { id: 4, name: 'Jeans', category: 'clothing', price: 79 }
  ];

  const filteredProducts = filter === 'all' 
    ? products 
    : products.filter(p => p.category === filter);

  return (
    <div>
      <div className="filters">
        <button onClick={() => setFilter('all')}>All</button>
        <button onClick={() => setFilter('electronics')}>Electronics</button>
        <button onClick={() => setFilter('clothing')}>Clothing</button>
      </div>
      
      <ul>
        {filteredProducts.map(product => (
          <li key={product.id}>
            {product.name} - ${product.price}
          </li>
        ))}
      </ul>
      
      {filteredProducts.length === 0 && (
        <p>No products match this filter.</p>
      )}
    </div>
  );
}
```

***

## Sorting Lists

```javascript theme={null}
function SortableTable() {
  const [sortConfig, setSortConfig] = useState({ key: 'name', direction: 'asc' });
  
  const users = [
    { id: 1, name: 'Charlie', age: 30, email: 'charlie@test.com' },
    { id: 2, name: 'Alice', age: 25, email: 'alice@test.com' },
    { id: 3, name: 'Bob', age: 35, email: 'bob@test.com' }
  ];

  const sortedUsers = [...users].sort((a, b) => {
    if (a[sortConfig.key] < b[sortConfig.key]) {
      return sortConfig.direction === 'asc' ? -1 : 1;
    }
    if (a[sortConfig.key] > b[sortConfig.key]) {
      return sortConfig.direction === 'asc' ? 1 : -1;
    }
    return 0;
  });

  const handleSort = (key) => {
    setSortConfig(prev => ({
      key,
      direction: prev.key === key && prev.direction === 'asc' ? 'desc' : 'asc'
    }));
  };

  const SortIcon = ({ column }) => {
    if (sortConfig.key !== column) return '↕️';
    return sortConfig.direction === 'asc' ? '↑' : '↓';
  };

  return (
    <table>
      <thead>
        <tr>
          <th onClick={() => handleSort('name')}>
            Name <SortIcon column="name" />
          </th>
          <th onClick={() => handleSort('age')}>
            Age <SortIcon column="age" />
          </th>
          <th onClick={() => handleSort('email')}>
            Email <SortIcon column="email" />
          </th>
        </tr>
      </thead>
      <tbody>
        {sortedUsers.map(user => (
          <tr key={user.id}>
            <td>{user.name}</td>
            <td>{user.age}</td>
            <td>{user.email}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}
```

***

## Search and Filter Combined

```javascript theme={null}
function SearchableList() {
  const [searchTerm, setSearchTerm] = useState('');
  
  const items = [
    { id: 1, title: 'React Fundamentals', tags: ['react', 'javascript'] },
    { id: 2, title: 'Node.js Basics', tags: ['node', 'javascript'] },
    { id: 3, title: 'CSS Grid Layout', tags: ['css', 'design'] },
    { id: 4, title: 'TypeScript Guide', tags: ['typescript', 'javascript'] }
  ];

  const filteredItems = items.filter(item => {
    const matchesTitle = item.title.toLowerCase().includes(searchTerm.toLowerCase());
    const matchesTags = item.tags.some(tag => 
      tag.toLowerCase().includes(searchTerm.toLowerCase())
    );
    return matchesTitle || matchesTags;
  });

  return (
    <div>
      <input
        type="text"
        placeholder="Search by title or tag..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      
      <p>{filteredItems.length} results found</p>
      
      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>
            <strong>{item.title}</strong>
            <div className="tags">
              {item.tags.map(tag => (
                <span key={tag} className="tag">{tag}</span>
              ))}
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}
```

***

## Nested Lists

```javascript theme={null}
function CategoryList() {
  const categories = [
    {
      id: 1,
      name: 'Electronics',
      products: [
        { id: 101, name: 'Laptop' },
        { id: 102, name: 'Phone' }
      ]
    },
    {
      id: 2,
      name: 'Clothing',
      products: [
        { id: 201, name: 'T-Shirt' },
        { id: 202, name: 'Jeans' }
      ]
    }
  ];

  return (
    <div>
      {categories.map(category => (
        <div key={category.id} className="category">
          <h2>{category.name}</h2>
          <ul>
            {category.products.map(product => (
              <li key={product.id}>{product.name}</li>
            ))}
          </ul>
        </div>
      ))}
    </div>
  );
}
```

<Note>
  **Keys only need to be unique among siblings.** In the example above, `product.id` can be the same number as `category.id` because they're in different levels.
</Note>

***

## Rendering Nothing for Some Items

Use `filter()` before `map()`, or return `null` from map:

```javascript theme={null}
function ActiveUsersList({ users }) {
  // Method 1: Filter then map (preferred)
  return (
    <ul>
      {users
        .filter(user => user.isActive)
        .map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
    </ul>
  );
}

function ConditionalList({ items }) {
  // Method 2: Return null for items to skip
  return (
    <ul>
      {items.map(item => {
        if (item.isHidden) return null;
        return <li key={item.id}>{item.name}</li>;
      })}
    </ul>
  );
}
```

***

## Empty States

Always handle the case when a list is empty:

```javascript theme={null}
function UserList({ users }) {
  if (users.length === 0) {
    return (
      <div className="empty-state">
        <img src="/empty.svg" alt="No users" />
        <h3>No users found</h3>
        <p>Try adjusting your search or filters.</p>
        <button>Clear filters</button>
      </div>
    );
  }

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
```

***

## Performance: Extracting List Items

For complex lists, extract the item into its own component:

```javascript theme={null}
// ✅ Good - Extracted component
function ProductCard({ product, onAddToCart }) {
  return (
    <div className="product-card">
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button onClick={() => onAddToCart(product.id)}>
        Add to Cart
      </button>
    </div>
  );
}

function ProductGrid({ products, onAddToCart }) {
  return (
    <div className="product-grid">
      {products.map(product => (
        <ProductCard
          key={product.id}
          product={product}
          onAddToCart={onAddToCart}
        />
      ))}
    </div>
  );
}
```

<Tip>
  **Performance Tip**: When a list item is its own component, you can wrap it with `React.memo()` to prevent re-renders when other items change.

  ```javascript theme={null}
  const ProductCard = React.memo(function ProductCard({ product, onAddToCart }) {
    // Only re-renders if product or onAddToCart changes
    return (/* ... */);
  });
  ```
</Tip>

***

## 🎯 Practice Exercises

<Accordion title="Exercise 1: Todo List with CRUD">
  ```javascript theme={null}
  function TodoApp() {
    const [todos, setTodos] = useState([
      { id: 1, text: 'Learn React', completed: false },
      { id: 2, text: 'Build project', completed: false }
    ]);
    const [input, setInput] = useState('');

    const addTodo = () => {
      if (!input.trim()) return;
      setTodos([...todos, {
        id: Date.now(),
        text: input,
        completed: false
      }]);
      setInput('');
    };

    const toggleTodo = (id) => {
      setTodos(todos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      ));
    };

    const deleteTodo = (id) => {
      setTodos(todos.filter(todo => todo.id !== id));
    };

    const pendingCount = todos.filter(t => !t.completed).length;

    return (
      <div>
        <h1>Todos ({pendingCount} pending)</h1>
        
        <div>
          <input
            value={input}
            onChange={e => setInput(e.target.value)}
            onKeyDown={e => e.key === 'Enter' && addTodo()}
            placeholder="Add todo..."
          />
          <button onClick={addTodo}>Add</button>
        </div>

        {todos.length === 0 ? (
          <p>No todos yet!</p>
        ) : (
          <ul>
            {todos.map(todo => (
              <li key={todo.id}>
                <input
                  type="checkbox"
                  checked={todo.completed}
                  onChange={() => toggleTodo(todo.id)}
                />
                <span style={{
                  textDecoration: todo.completed ? 'line-through' : 'none'
                }}>
                  {todo.text}
                </span>
                <button onClick={() => deleteTodo(todo.id)}>🗑️</button>
              </li>
            ))}
          </ul>
        )}
      </div>
    );
  }
  ```
</Accordion>

<Accordion title="Exercise 2: Filterable Contact List">
  ```javascript theme={null}
  function ContactList() {
    const [search, setSearch] = useState('');
    const [filter, setFilter] = useState('all'); // all, favorites

    const contacts = [
      { id: 1, name: 'Alice Johnson', phone: '555-0101', favorite: true },
      { id: 2, name: 'Bob Smith', phone: '555-0102', favorite: false },
      { id: 3, name: 'Charlie Brown', phone: '555-0103', favorite: true },
      { id: 4, name: 'Diana Ross', phone: '555-0104', favorite: false }
    ];

    const filteredContacts = contacts
      .filter(c => filter === 'all' || c.favorite)
      .filter(c => 
        c.name.toLowerCase().includes(search.toLowerCase()) ||
        c.phone.includes(search)
      );

    return (
      <div>
        <input
          type="text"
          placeholder="Search contacts..."
          value={search}
          onChange={e => setSearch(e.target.value)}
        />
        
        <div>
          <button 
            className={filter === 'all' ? 'active' : ''}
            onClick={() => setFilter('all')}
          >
            All
          </button>
          <button 
            className={filter === 'favorites' ? 'active' : ''}
            onClick={() => setFilter('favorites')}
          >
            ⭐ Favorites
          </button>
        </div>

        <ul>
          {filteredContacts.map(contact => (
            <li key={contact.id}>
              {contact.favorite && '⭐'} {contact.name} - {contact.phone}
            </li>
          ))}
        </ul>
        
        {filteredContacts.length === 0 && (
          <p>No contacts found.</p>
        )}
      </div>
    );
  }
  ```
</Accordion>

<Accordion title="Exercise 3: Reorderable List">
  ```javascript theme={null}
  function ReorderableList() {
    const [items, setItems] = useState([
      { id: 1, text: 'First item' },
      { id: 2, text: 'Second item' },
      { id: 3, text: 'Third item' },
      { id: 4, text: 'Fourth item' }
    ]);

    const moveUp = (index) => {
      if (index === 0) return;
      const newItems = [...items];
      [newItems[index - 1], newItems[index]] = [newItems[index], newItems[index - 1]];
      setItems(newItems);
    };

    const moveDown = (index) => {
      if (index === items.length - 1) return;
      const newItems = [...items];
      [newItems[index], newItems[index + 1]] = [newItems[index + 1], newItems[index]];
      setItems(newItems);
    };

    return (
      <ul>
        {items.map((item, index) => (
          <li key={item.id} style={{ display: 'flex', gap: '10px', padding: '8px' }}>
            <span>{item.text}</span>
            <button 
              onClick={() => moveUp(index)}
              disabled={index === 0}
            >
              ↑
            </button>
            <button 
              onClick={() => moveDown(index)}
              disabled={index === items.length - 1}
            >
              ↓
            </button>
          </li>
        ))}
      </ul>
    );
  }
  ```
</Accordion>

***

## Summary

| Concept              | Description                                                   |
| -------------------- | ------------------------------------------------------------- |
| `map()`              | Transform array data into React elements                      |
| `key`                | Unique identifier for list items (required)                   |
| ID as key            | Best practice - use database IDs or stable unique identifiers |
| Index as key         | Only for static lists that never reorder                      |
| `filter()`           | Filter items before rendering                                 |
| `sort()`             | Always use `[...arr].sort()` (don't mutate)                   |
| Empty states         | Handle zero results gracefully                                |
| Nested lists         | Keys only need to be unique among siblings                    |
| Extracted components | Better organization and potential performance gains           |

<Card title="Next Steps" icon="arrow-right">
  In the next chapter, you'll learn about **Forms & Controlled Components** — handling user input effectively!
</Card>

***

## Interview Deep-Dive

<AccordionGroup>
  <Accordion title="You have a list of 10,000 items that users can reorder via drag-and-drop. Using array index as the key, users report that input fields inside list items lose their text on reorder. Diagnose the issue and fix it.">
    **Strong Answer:**
    This is a classic key identity bug. When you use array index as the key, React maps keys to component instances. Before the drag, key=0 corresponds to item A, key=1 to item B. After dragging item B to position 0, key=0 now corresponds to item B, but React still has the component instance from key=0 -- which holds item A's internal state (the text in the input field). React sees the same key at the same position and reuses the component, updating its props but keeping its internal state. The result: item B's row shows item A's input text.

    The fix is using a stable, unique identifier as the key -- the item's database ID, UUID, or any value that is tied to the item's identity rather than its position. With `key={item.id}`, when item B moves to position 0, React sees key="b-uuid" at position 0 (which was previously at position 1) and moves the existing component instance with its correct state.

    For the performance angle with 10,000 items: proper keys combined with `React.memo` on the list item component means React only updates the items that actually moved -- typically just 2 items in a single drag operation. With index keys, React would diff and potentially update all 10,000 items because every key-to-data mapping changed.

    In production, I would also add list virtualization (react-window or react-virtuoso) for 10,000 items. You only render the visible 20-50 items, so even if reconciliation is triggered, only a handful of components participate.

    **Follow-up: Can you ever safely use index as a key? What are the exact conditions?**

    Three conditions must all be true: the list is static (items are never added, removed, or reordered), the items have no internal state (no inputs, no expanded/collapsed states), and the items are not referenced by other components. A list of static labels that never changes meets all three. A navigation menu with fixed items meets all three.

    The moment any condition is violated, index keys become dangerous. In practice, I default to unique IDs because the cost is negligible and the safety benefit is significant. The only exception I make is for truly ephemeral rendering where the list is generated fresh each time and has no interactivity -- like rendering pagination dots or star ratings.
  </Accordion>

  <Accordion title="How does React's reconciliation algorithm handle list reordering differently from list insertion? What is the time complexity?">
    **Strong Answer:**
    React's reconciliation algorithm for lists is essentially a keyed diffing algorithm. It builds a map of old keys to old fiber nodes, then iterates through the new list of keys. For each new key, it either finds a match in the old map (reuse the fiber, possibly move it in the DOM) or creates a new fiber.

    For insertion at the end, React iterates through the list, finds all existing keys match, then sees a new key at the end. It creates one new DOM node. This is O(n) for the comparison plus O(1) for the insertion -- effectively O(n) total.

    For reordering (say, moving the last item to the first position), React identifies that all existing keys are still present but in different positions. It reuses all fiber nodes but must determine which DOM nodes to move. React uses a "last placed index" heuristic: it scans the new list left to right, tracking the highest index from the old list it has seen so far. If the current item's old index is less than the last placed index, it needs to move. This is O(n) with minimal DOM operations -- typically moving only the items that shifted.

    For insertion at the beginning (the worst case for index keys), with proper keys React creates one new node and moves nothing -- the existing nodes stay in place and the new node is inserted at position 0. With index keys, React sees that the data at every index changed and updates every node's content -- O(n) DOM writes instead of O(1).

    The key insight: React's diffing is always O(n) for the comparison pass, but the number of actual DOM mutations depends heavily on the quality of keys. Good keys minimize mutations; bad keys maximize them.

    **Follow-up: How would you benchmark the actual rendering performance difference between index keys and ID keys on a list of 5,000 items with frequent reordering?**

    I would use the React DevTools Profiler in combination with the browser Performance panel. In the Profiler, record a drag-and-drop operation with index keys, noting the number of components that re-rendered and the total render time. Then switch to ID keys and record the same operation. The Profiler's flame graph shows exactly which components rendered and how long each took.

    For DOM-level measurement, the Performance panel's "Rendering" tab lets you enable Paint Flashing, which highlights every DOM region that was repainted. With index keys you would see the entire list flash; with ID keys, only the moved items flash.

    For automated benchmarking, I would use `performance.mark()` and `performance.measure()` around the state update, or React's `Profiler` component with an `onRender` callback that logs commit times. Run each scenario 50 times and compare the median commit duration. On a 5,000-item list with frequent reordering, I would expect ID keys to be 10-50x faster in DOM mutations, though the JavaScript diffing cost remains similar.
  </Accordion>

  <Accordion title="When rendering a filtered or sorted list, should you filter/sort in the render body, in useMemo, or in a useEffect? Explain the tradeoffs.">
    **Strong Answer:**
    The correct default is computing it in the render body -- just a variable assignment: `const filtered = items.filter(i => i.active)`. This is simple, always up to date, and for most list sizes (under a few hundred items), the performance cost is negligible. React will run this on every render, but filtering an array of 100 objects takes microseconds.

    The `useMemo` approach -- `const filtered = useMemo(() => items.filter(i => i.active), [items])` -- is appropriate when the computation is genuinely expensive. Sorting 10,000 objects with a complex comparator, chaining multiple filters, or computing derived aggregations (group by, reduce) are good candidates. `useMemo` caches the result and only recomputes when `items` changes, skipping the work on unrelated re-renders (like typing in a search field that does not affect the items array).

    The `useEffect` approach -- storing filtered results in separate state -- is almost always wrong. It introduces an unnecessary state variable, causes an extra render (the first render has stale filtered data, the effect runs after paint and triggers a second render with correct data), and creates a synchronization bug if you forget a dependency. The React docs explicitly call this an anti-pattern: "If you can calculate something from the existing props or state, don't put it in state. Instead, calculate it during rendering."

    The one legitimate use of `useEffect` for derived data is when the derivation has an asynchronous step -- like filtering requires a server call. But that is data fetching, not derivation.

    **Follow-up: If you use useMemo for a filtered list, what happens if the items array is the same data but a new reference every render? How do you fix this?**

    If the parent recreates the array on every render (e.g., `items={data.map(x => ({ ...x }))}`) the useMemo re-runs every time because `[items]` has a new reference. The memoization is worthless.

    The fix depends on where the array comes from. If it comes from a parent via props, the parent should memoize it: `const items = useMemo(() => data.map(transform), [data])`. If it comes from a Redux selector, `useSelector` already uses reference equality by default, so the selector should return the same reference if the underlying data has not changed -- use `createSelector` from Reselect for derived data. If it comes from a `useState`, ensure you only call `setState` with a new array when the data actually changes, not on every render.

    The general principle: memoization only helps when the inputs are stable. If the inputs are new on every render, memoization adds overhead (comparing dependencies) without saving any work.
  </Accordion>
</AccordionGroup>
