Skip
Arish's avatar

7. Lists and Keys


Rendering Lists in React

Lists are a fundamental part of most applications. React makes it easy to render dynamic lists of data.

Basic List Rendering

Use map() to transform arrays into elements:

jsx
1function NumberList() {
2  const numbers = [1, 2, 3, 4, 5]
3  
4  return (
5    <ul>
6      {numbers.map((number) => (
7        <li key={number}>{number}</li>
8      ))}
9    </ul>
10  )
11}

The Key Prop

Keys help React identify which items have changed, been added, or removed:

jsx
1// ✅ Good - unique, stable key
2const users = [
3  { id: 1, name: 'Alice' },
4  { id: 2, name: 'Bob' },
5  { id: 3, name: 'Charlie' }
6]
7
8function UserList() {
9  return (
10    <ul>
11      {users.map((user) => (
12        <li key={user.id}>{user.name}</li>
13      ))}
14    </ul>
15  )
16}

Key Rules

jsx
1// ✅ Use unique IDs from your data
2{items.map(item => <Item key={item.id} {...item} />)}
3
4// ⚠️ Index as key (only when items won't reorder)
5{items.map((item, index) => <Item key={index} {...item} />)}
6
7// ❌ Don't use random values
8{items.map(item => <Item key={Math.random()} {...item} />)}
9
10// ❌ Don't use non-unique values
11{items.map(item => <Item key={item.name} {...item} />)} // Names might duplicate

Why Keys Matter

jsx
1// Without proper keys, React can't optimize re-renders
2
3// Initial list:
4<li key="1">Apple</li>
5<li key="2">Banana</li>
6<li key="3">Cherry</li>
7
8// After adding "Date" at start:
9<li key="0">Date</li>    // New
10<li key="1">Apple</li>   // Same key, React knows it's the same item
11<li key="2">Banana</li>  // Same key
12<li key="3">Cherry</li>  // Same key
13
14// With index as key (bad for insertions):
15<li key="0">Date</li>    // Key 0 had Apple, React thinks content changed
16<li key="1">Apple</li>   // Key 1 had Banana, React re-renders
17<li key="2">Banana</li>  // Key 2 had Cherry, React re-renders
18<li key="3">Cherry</li>  // New key, new element

Extracting List Components

jsx
1// List item component
2function TodoItem({ todo, onToggle, onDelete }) {
3  return (
4    <li className={todo.completed ? 'completed' : ''}>
5      <span onClick={() => onToggle(todo.id)}>
6        {todo.text}
7      </span>
8      <button onClick={() => onDelete(todo.id)}>Delete</button>
9    </li>
10  )
11}
12
13// List component
14function TodoList({ todos, onToggle, onDelete }) {
15  return (
16    <ul>
17      {todos.map((todo) => (
18        <TodoItem 
19          key={todo.id}
20          todo={todo}
21          onToggle={onToggle}
22          onDelete={onDelete}
23        />
24      ))}
25    </ul>
26  )
27}

Empty States

Handle empty lists gracefully:

jsx
1function ProductList({ products }) {
2  if (products.length === 0) {
3    return <p>No products found.</p>
4  }
5  
6  return (
7    <ul>
8      {products.map((product) => (
9        <li key={product.id}>{product.name}</li>
10      ))}
11    </ul>
12  )
13}
14
15// Or inline
16function ProductList({ products }) {
17  return (
18    <div>
19      {products.length === 0 ? (
20        <p>No products found.</p>
21      ) : (
22        <ul>
23          {products.map((product) => (
24            <li key={product.id}>{product.name}</li>
25          ))}
26        </ul>
27      )}
28    </div>
29  )
30}

Filtering Lists

jsx
1function FilteredList() {
2  const [filter, setFilter] = useState('')
3  const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']
4  
5  const filteredItems = items.filter(item =>
6    item.toLowerCase().includes(filter.toLowerCase())
7  )
8  
9  return (
10    <div>
11      <input
12        value={filter}
13        onChange={(e) => setFilter(e.target.value)}
14        placeholder="Filter items..."
15      />
16      
17      <ul>
18        {filteredItems.map((item) => (
19          <li key={item}>{item}</li>
20        ))}
21      </ul>
22      
23      {filteredItems.length === 0 && (
24        <p>No items match "{filter}"</p>
25      )}
26    </div>
27  )
28}

Sorting Lists

jsx
1function SortableList() {
2  const [sortOrder, setSortOrder] = useState('asc')
3  const items = [
4    { id: 1, name: 'Banana', price: 1.5 },
5    { id: 2, name: 'Apple', price: 2.0 },
6    { id: 3, name: 'Cherry', price: 3.5 }
7  ]
8  
9  const sortedItems = [...items].sort((a, b) => {
10    if (sortOrder === 'asc') {
11      return a.name.localeCompare(b.name)
12    }
13    return b.name.localeCompare(a.name)
14  })
15  
16  return (
17    <div>
18      <button onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}>
19        Sort {sortOrder === 'asc' ? '↓' : '↑'}
20      </button>
21      
22      <ul>
23        {sortedItems.map((item) => (
24          <li key={item.id}>
25            {item.name} - ${item.price}
26          </li>
27        ))}
28      </ul>
29    </div>
30  )
31}

Nested Lists

jsx
1function NestedList() {
2  const categories = [
3    {
4      id: 1,
5      name: 'Fruits',
6      items: ['Apple', 'Banana', 'Cherry']
7    },
8    {
9      id: 2,
10      name: 'Vegetables',
11      items: ['Carrot', 'Broccoli', 'Spinach']
12    }
13  ]
14  
15  return (
16    <div>
17      {categories.map((category) => (
18        <div key={category.id}>
19          <h3>{category.name}</h3>
20          <ul>
21            {category.items.map((item) => (
22              <li key={item}>{item}</li>
23            ))}
24          </ul>
25        </div>
26      ))}
27    </div>
28  )
29}

Keys Must Be Unique Among Siblings

Keys only need to be unique among siblings, not globally:

jsx
1function App() {
2  return (
3    <div>
4      {/* These can have the same keys */}
5      <ul>
6        {items1.map(item => <li key={item.id}>{item.name}</li>)}
7      </ul>
8      
9      <ul>
10        {items2.map(item => <li key={item.id}>{item.name}</li>)}
11      </ul>
12    </div>
13  )
14}

Generating Keys

When your data doesn't have IDs:

jsx
1// Option 1: Use a counter (careful with SSR)
2let nextId = 0
3function generateId() {
4  return nextId++
5}
6
7// Option 2: UUID library
8import { v4 as uuidv4 } from 'uuid'
9const newItem = { id: uuidv4(), name: 'New Item' }
10
11// Option 3: Use a hash of the content
12import { hash } from 'some-hash-library'
13const key = hash(item.content)

Lists and keys are essential for building dynamic React applications!