Lists & Keys

Rendering lists of data and understanding keys

Rendering Lists

In React, you render lists by using JavaScript's map() method to transform an array of data into an array of JSX elements. This is one of the most common patterns in React.

Basic List Rendering

function FruitList() {
  const fruits = ['Apple', 'Banana', 'Cherry', 'Date'];

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

// Rendering with index (when items don't have unique id)
function NumberList() {
  const numbers = [1, 2, 3, 4, 5];

  return (
    <ul>
      {numbers.map((num, index) => (
        <li key={index}>Number: {num}</li>
      ))}
    </ul>
  );
}

Why Keys Matter

Keys help React identify which items have changed, been added, or removed. They give elements a stable identity across re-renders, allowing React to update the DOM efficiently.

Rendering Objects

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 (
    <div>
      {users.map((user) => (
        <div key={user.id} className="user-card">
          <h3>{user.name}</h3>
          <p>{user.email}</p>
        </div>
      ))}
    </div>
  );
}

Key Rules

Rule Explanation
Must be unique Keys must be unique among siblings (not globally)
Must be stable Keys shouldn't change between renders
Avoid index as key Only use index if list is static and won't reorder
Use data IDs Best to use unique IDs from your data

Good vs Bad Keys

function TodoList({ todos }) {
  return (
    <ul>
      {/* ✓ GOOD: Using unique ID from data */}
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}

      {/* ✗ BAD: Using index (problematic if list reorders) */}
      {todos.map((todo, index) => (
        <li key={index}>{todo.text}</li>
      ))}

      {/* ✗ BAD: Using random values (changes every render!) */}
      {todos.map(todo => (
        <li key={Math.random()}>{todo.text}</li>
      ))}
    </ul>
  );
}

⚠️ When Index is OK

Using index as key is acceptable when:
• The list is static (never changes)
• Items are never reordered or filtered
• Items have no stable IDs

Extracting List Items to Components

// TodoItem component
function TodoItem({ todo, onToggle, onDelete }) {
  return (
    <li className={todo.done ? 'completed' : ''}>
      <input
        type="checkbox"
        checked={todo.done}
        onChange={() => onToggle(todo.id)}
      />
      <span>{todo.text}</span>
      <button onClick={() => onDelete(todo.id)}>Delete</button>
    </li>
  );
}

// Parent component renders the list
function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React', done: false },
    { id: 2, text: 'Build project', done: false }
  ]);

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

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

  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}  {/* Key goes on the outermost element in map */}
          todo={todo}
          onToggle={handleToggle}
          onDelete={handleDelete}
        />
      ))}
    </ul>
  );
}

Filtering Lists

function FilteredList() {
  const [filter, setFilter] = useState('all');
  const items = [
    { id: 1, name: 'Apple', category: 'fruit' },
    { id: 2, name: 'Carrot', category: 'vegetable' },
    { id: 3, name: 'Banana', category: 'fruit' },
    { id: 4, name: 'Broccoli', category: 'vegetable' }
  ];

  const filteredItems = filter === 'all' 
    ? items 
    : items.filter(item => item.category === filter);

  return (
    <div>
      <select value={filter} onChange={e => setFilter(e.target.value)}>
        <option value="all">All</option>
        <option value="fruit">Fruits</option>
        <option value="vegetable">Vegetables</option>
      </select>

      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

Sorting Lists

function SortableList() {
  const [sortBy, setSortBy] = useState('name');
  const [sortOrder, setSortOrder] = useState('asc');
  
  const users = [
    { id: 1, name: 'Charlie', age: 25 },
    { id: 2, name: 'Alice', age: 30 },
    { id: 3, name: 'Bob', age: 20 }
  ];

  const sortedUsers = [...users].sort((a, b) => {
    const aVal = a[sortBy];
    const bVal = b[sortBy];
    
    if (sortOrder === 'asc') {
      return aVal > bVal ? 1 : -1;
    }
    return aVal < bVal ? 1 : -1;
  });

  return (
    <div>
      <button onClick={() => setSortBy('name')}>Sort by Name</button>
      <button onClick={() => setSortBy('age')}>Sort by Age</button>
      <button onClick={() => setSortOrder(o => o === 'asc' ? 'desc' : 'asc')}>
        {sortOrder === 'asc' ? '↑' : '↓'}
      </button>

      <ul>
        {sortedUsers.map(user => (
          <li key={user.id}>{user.name} ({user.age})</li>
        ))}
      </ul>
    </div>
  );
}

Empty State

function ItemList({ items }) {
  if (items.length === 0) {
    return (
      <div className="empty-state">
        <p>No items found.</p>
        <button>Add your first item</button>
      </div>
    );
  }

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Nested Lists

function CategoryList() {
  const categories = [
    {
      id: 1,
      name: 'Electronics',
      items: [
        { id: 101, name: 'Phone' },
        { id: 102, name: 'Laptop' }
      ]
    },
    {
      id: 2,
      name: 'Clothing',
      items: [
        { id: 201, name: 'Shirt' },
        { id: 202, name: 'Pants' }
      ]
    }
  ];

  return (
    <div>
      {categories.map(category => (
        <div key={category.id}>
          <h3>{category.name}</h3>
          <ul>
            {category.items.map(item => (
              <li key={item.id}>{item.name}</li>
            ))}
          </ul>
        </div>
      ))}
    </div>
  );
}

🎯 Lists & Keys Best Practices

  • ✓ Always provide a key prop when mapping arrays
  • ✓ Use unique, stable IDs from your data
  • ✓ Avoid using array index as key when list can change
  • ✓ Never use random values as keys
  • ✓ Extract list items into separate components
  • ✓ Handle empty states gracefully
  • ✓ Key goes on the outermost element returned by map()