Rendering Lists with Map in React

Creating Dynamic Collections of Components

Overview

Welcome to the world of dynamic list rendering! Just as a chef prepares multiple similar dishes efficiently using a recipe template, React uses the map() method to efficiently render collections of similar components. Today, you'll learn how to transform arrays of data into arrays of React elements, making your applications dynamic and data-driven.

The Map Method: From Arrays to Components

The map() method is like a factory assembly line - it takes raw materials (data) and transforms them into finished products (React components) using a consistent process.

graph LR A[Array of Data] --> B[map() Function] B --> C[Array of React Elements] C --> D[Rendered Components] E[Item 1] --> F[Component 1] G[Item 2] --> H[Component 2] I[Item 3] --> J[Component 3] style B fill:#f9f,stroke:#333,stroke-width:2px style C fill:#9cf,stroke:#333,stroke-width:2px

Basic List Rendering

Simple List Example


function SimpleList() {
    const fruits = ['Apple', 'Banana', 'Orange', 'Mango', 'Pineapple'];
    
    return (
        <ul>
            {fruits.map((fruit, index) => (
                <li key={index}>{fruit}</li>
            ))}
        </ul>
    );
}

// What's happening:
// 1. We have an array of strings
// 2. map() transforms each string into a <li> element
// 3. React renders the array of elements
            

Rendering Objects


function ProductList() {
    const products = [
        { id: 1, name: 'Laptop', price: 999 },
        { id: 2, name: 'Phone', price: 599 },
        { id: 3, name: 'Tablet', price: 399 }
    ];
    
    return (
        <div className="product-list">
            {products.map(product => (
                <div key={product.id} className="product-card">
                    <h3>{product.name}</h3>
                    <p>Price: ${product.price}</p>
                    <button>Add to Cart</button>
                </div>
            ))}
        </div>
    );
}
            

The Importance of Keys

Keys are like unique ID cards for components - they help React identify which items have changed, been added, or been removed.

graph TD A[React Virtual DOM] --> B{Compare with Previous} B --> C[Items with Keys] B --> D[Items without Keys] C --> E[Efficient Updates] D --> F[Re-render Everything] E --> G[Better Performance] F --> H[Poor Performance] style C fill:#9f9,stroke:#333,stroke-width:2px style D fill:#f99,stroke:#333,stroke-width:2px

Good Key Practices


// ✅ GOOD: Using unique IDs as keys
function GoodKeyExample() {
    const users = [
        { id: 'user-1', name: 'Alice' },
        { id: 'user-2', name: 'Bob' },
        { id: 'user-3', name: 'Charlie' }
    ];
    
    return (
        <ul>
            {users.map(user => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
}

// ❌ BAD: Using index as key (when list can change)
function BadKeyExample() {
    const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
    
    // Problematic when items are reordered, added, or removed
    return (
        <ul>
            {items.map((item, index) => (
                <li key={index}>
                    <input defaultValue={item} />
                </li>
            ))}
        </ul>
    );
}

// ✅ GOOD: Generating stable keys for data without IDs
function GeneratedKeyExample() {
    const items = ['Apple', 'Banana', 'Orange'];
    
    // Use a combination of values to create unique keys
    return (
        <ul>
            {items.map((item, index) => (
                <li key={`${item}-${index}`}>{item}</li>
            ))}
        </ul>
    );
}
            

Complex List Rendering

Nested Lists


function NestedList() {
    const categories = [
        {
            id: 1,
            name: 'Fruits',
            items: ['Apple', 'Banana', 'Orange']
        },
        {
            id: 2,
            name: 'Vegetables',
            items: ['Carrot', 'Broccoli', 'Spinach']
        }
    ];
    
    return (
        <div>
            {categories.map(category => (
                <div key={category.id}>
                    <h3>{category.name}</h3>
                    <ul>
                        {category.items.map((item, index) => (
                            <li key={`${category.id}-${index}`}>
                                {item}
                            </li>
                        ))}
                    </ul>
                </div>
            ))}
        </div>
    );
}
            

Rendering Components from Lists


// Product component
function Product({ name, price, inStock }) {
    return (
        <div className="product">
            <h3>{name}</h3>
            <p>Price: ${price}</p>
            <p className={inStock ? 'in-stock' : 'out-of-stock'}>
                {inStock ? 'In Stock' : 'Out of Stock'}
            </p>
        </div>
    );
}

// Product list using the Product component
function ProductGrid() {
    const products = [
        { id: 1, name: 'Laptop', price: 999, inStock: true },
        { id: 2, name: 'Phone', price: 599, inStock: false },
        { id: 3, name: 'Tablet', price: 399, inStock: true }
    ];
    
    return (
        <div className="product-grid">
            {products.map(product => (
                <Product
                    key={product.id}
                    name={product.name}
                    price={product.price}
                    inStock={product.inStock}
                />
            ))}
        </div>
    );
}
            

Advanced Mapping Techniques

Filtering and Mapping


function FilteredList() {
    const products = [
        { id: 1, name: 'Laptop', price: 999, category: 'electronics' },
        { id: 2, name: 'Shirt', price: 29, category: 'clothing' },
        { id: 3, name: 'Phone', price: 599, category: 'electronics' },
        { id: 4, name: 'Shoes', price: 89, category: 'clothing' }
    ];
    
    const [filter, setFilter] = useState('all');
    
    const filteredProducts = products
        .filter(product => filter === 'all' || product.category === filter)
        .map(product => (
            <div key={product.id} className="product">
                <h3>{product.name}</h3>
                <p>${product.price}</p>
                <p>Category: {product.category}</p>
            </div>
        ));
    
    return (
        <div>
            <select 
                value={filter} 
                onChange={(e) => setFilter(e.target.value)}
            >
                <option value="all">All Categories</option>
                <option value="electronics">Electronics</option>
                <option value="clothing">Clothing</option>
            </select>
            
            <div className="products">
                {filteredProducts}
            </div>
        </div>
    );
}
            

Sorting and Mapping


function SortableList() {
    const [users, setUsers] = useState([
        { id: 1, name: 'Alice', age: 30 },
        { id: 2, name: 'Bob', age: 25 },
        { id: 3, name: 'Charlie', age: 35 }
    ]);
    
    const [sortBy, setSortBy] = useState('name');
    
    const sortedUsers = [...users].sort((a, b) => {
        if (sortBy === 'name') {
            return a.name.localeCompare(b.name);
        } else if (sortBy === 'age') {
            return a.age - b.age;
        }
        return 0;
    });
    
    return (
        <div>
            <select 
                value={sortBy} 
                onChange={(e) => setSortBy(e.target.value)}
            >
                <option value="name">Sort by Name</option>
                <option value="age">Sort by Age</option>
            </select>
            
            <ul>
                {sortedUsers.map(user => (
                    <li key={user.id}>
                        {user.name} - Age: {user.age}
                    </li>
                ))}
            </ul>
        </div>
    );
}
            

Dynamic List Management

Adding and Removing Items


function TodoList() {
    const [todos, setTodos] = useState([
        { id: 1, text: 'Learn React', completed: false },
        { id: 2, text: 'Build a project', completed: false }
    ]);
    const [inputValue, setInputValue] = useState('');
    
    const addTodo = () => {
        if (inputValue.trim()) {
            setTodos([
                ...todos,
                {
                    id: Date.now(),
                    text: inputValue,
                    completed: false
                }
            ]);
            setInputValue('');
        }
    };
    
    const toggleTodo = (id) => {
        setTodos(todos.map(todo =>
            todo.id === id
                ? { ...todo, completed: !todo.completed }
                : todo
        ));
    };
    
    const removeTodo = (id) => {
        setTodos(todos.filter(todo => todo.id !== id));
    };
    
    return (
        <div>
            <div>
                <input
                    value={inputValue}
                    onChange={(e) => setInputValue(e.target.value)}
                    placeholder="Add a todo"
                />
                <button onClick={addTodo}>Add</button>
            </div>
            
            <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={() => removeTodo(todo.id)}>
                            Delete
                        </button>
                    </li>
                ))}
            </ul>
        </div>
    );
}
            

Performance Optimization

Memoizing List Items


// Expensive list item component
const ExpensiveListItem = React.memo(({ item, onToggle, onRemove }) => {
    console.log('Rendering item:', item.id);
    
    return (
        <li>
            <input
                type="checkbox"
                checked={item.completed}
                onChange={() => onToggle(item.id)}
            />
            <span>{item.text}</span>
            <button onClick={() => onRemove(item.id)}>Remove</button>
        </li>
    );
});

function OptimizedList() {
    const [items, setItems] = useState([
        { id: 1, text: 'Item 1', completed: false },
        { id: 2, text: 'Item 2', completed: true }
    ]);
    
    const handleToggle = useCallback((id) => {
        setItems(items => items.map(item =>
            item.id === id
                ? { ...item, completed: !item.completed }
                : item
        ));
    }, []);
    
    const handleRemove = useCallback((id) => {
        setItems(items => items.filter(item => item.id !== id));
    }, []);
    
    return (
        <ul>
            {items.map(item => (
                <ExpensiveListItem
                    key={item.id}
                    item={item}
                    onToggle={handleToggle}
                    onRemove={handleRemove}
                />
            ))}
        </ul>
    );
}
            

Handling Empty States


function ListWithEmptyState() {
    const [items, setItems] = useState([]);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        setLoading(true);
        fetchItems()
            .then(data => {
                setItems(data);
                setLoading(false);
            })
            .catch(err => {
                setError(err.message);
                setLoading(false);
            });
    }, []);
    
    if (loading) {
        return <div>Loading...</div>;
    }
    
    if (error) {
        return <div>Error: {error}</div>;
    }
    
    if (items.length === 0) {
        return (
            <div className="empty-state">
                <p>No items found</p>
                <button onClick={() => window.location.reload()}>
                    Refresh
                </button>
            </div>
        );
    }
    
    return (
        <ul>
            {items.map(item => (
                <li key={item.id}>{item.name}</li>
            ))}
        </ul>
    );
}
            

Virtual Lists for Large Datasets


// Concept of virtual list (simplified)
function VirtualList({ items, itemHeight, windowHeight }) {
    const [scrollTop, setScrollTop] = useState(0);
    
    const startIndex = Math.floor(scrollTop / itemHeight);
    const endIndex = Math.min(
        startIndex + Math.ceil(windowHeight / itemHeight),
        items.length
    );
    
    const visibleItems = items.slice(startIndex, endIndex);
    
    return (
        <div 
            style={{ 
                height: windowHeight, 
                overflow: 'auto' 
            }}
            onScroll={(e) => setScrollTop(e.target.scrollTop)}
        >
            <div style={{ height: items.length * itemHeight }}>
                <div style={{ 
                    position: 'relative',
                    top: startIndex * itemHeight 
                }}>
                    {visibleItems.map((item, index) => (
                        <div 
                            key={startIndex + index}
                            style={{ height: itemHeight }}
                        >
                            {item.name}
                        </div>
                    ))}
                </div>
            </div>
        </div>
    );
}
            

Common Patterns and Best Practices

graph TD A[List Rendering Best Practices] --> B[Use Stable Keys] A --> C[Avoid Index as Key] A --> D[Memoize Expensive Items] A --> E[Handle Empty States] A --> F[Optimize Re-renders] B --> G[Use unique IDs] C --> H[Unless list is static] D --> I[React.memo for items] E --> J[Loading/Error states] F --> K[useCallback handlers] style A fill:#f9f,stroke:#333,stroke-width:2px

Rendering Complex Structures


function CommentThread() {
    const comments = [
        {
            id: 1,
            author: 'Alice',
            text: 'Great article!',
            replies: [
                {
                    id: 2,
                    author: 'Bob',
                    text: 'I agree!',
                    replies: []
                }
            ]
        },
        {
            id: 3,
            author: 'Charlie',
            text: 'Thanks for sharing',
            replies: []
        }
    ];
    
    const renderComment = (comment, depth = 0) => (
        <div 
            key={comment.id} 
            style={{ marginLeft: depth * 20 }}
            className="comment"
        >
            <div className="comment-header">
                <strong>{comment.author}</strong>
            </div>
            <div className="comment-text">
                {comment.text}
            </div>
            {comment.replies.length > 0 && (
                <div className="replies">
                    {comment.replies.map(reply => 
                        renderComment(reply, depth + 1)
                    )}
                </div>
            )}
        </div>
    );
    
    return (
        <div className="comment-thread">
            {comments.map(comment => renderComment(comment))}
        </div>
    );
}
            

Error Boundaries for Lists


class ListItemErrorBoundary extends React.Component {
    state = { hasError: false };
    
    static getDerivedStateFromError(error) {
        return { hasError: true };
    }
    
    componentDidCatch(error, errorInfo) {
        console.error('List item error:', error, errorInfo);
    }
    
    render() {
        if (this.state.hasError) {
            return <li>Error loading item</li>;
        }
        
        return this.props.children;
    }
}

function SafeList({ items }) {
    return (
        <ul>
            {items.map(item => (
                <ListItemErrorBoundary key={item.id}>
                    <ListItem item={item} />
                </ListItemErrorBoundary>
            ))}
        </ul>
    );
}
            

Practice Exercises

Exercise 1: Product Catalog

Create a product catalog with filtering, sorting, and search:


function ProductCatalog() {
    // Create a component that:
    // - Renders a list of products
    // - Allows filtering by category
    // - Sorts by price/name
    // - Includes search functionality
    // - Shows product details on click
}
            

Exercise 2: Dynamic Table

Build a dynamic table component with sortable columns:


function DynamicTable({ columns, data }) {
    // Create a component that:
    // - Renders data in a table format
    // - Supports sorting by column
    // - Handles pagination
    // - Allows row selection
    // - Includes column visibility toggle
}
            

Exercise 3: Nested Menu

Create a nested navigation menu with expand/collapse functionality:


function NestedMenu({ menuItems }) {
    // Create a component that:
    // - Renders nested menu items
    // - Supports expand/collapse
    // - Highlights active item
    // - Handles deep nesting
    // - Includes keyboard navigation
}
            

Key Takeaways

What's Next?

In our next lesson, we'll explore the importance of keys in React and dive deeper into how React uses them to optimize rendering performance. You'll learn advanced key strategies and how to handle complex list scenarios!

Homework