Lists, Keys, and Dynamic Rendering

1. Array Rendering and map() Patterns

Pattern Syntax Description Use Case
Basic map() array.map(item => JSX) Transform array to JSX elements Simple list rendering
Index parameter array.map((item, i) => JSX) Access index in callback Numbered lists, positioning
Array parameter array.map((item, i, arr) => JSX) Access full array Context-aware rendering
Inline return {items.map(i => <div />)} Direct JSX return Concise single elements
Block return {items.map(i => { return <div /> })} Explicit return statement Multiple operations before return
Fragment wrapping map(i => <>...</>) Multiple elements without wrapper Flat DOM structure
Destructuring map(({id, name}) => JSX) Extract properties directly Cleaner variable access

Example: Basic list rendering patterns

// Simple list rendering
const UserList = ({ users }) => (
  <ul>
    {users.map(user => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);

// With index for numbering
const NumberedList = ({ items }) => (
  <ol>
    {items.map((item, index) => (
      <li key={item.id}>
        #{index + 1}: {item.text}
      </li>
    ))}
  </ol>
);

// Destructuring in map
const ProductList = ({ products }) => (
  <div>
    {products.map(({ id, name, price, stock }) => (
      <div key={id} className="product">
        <h3>{name}</h3>
        <p>${price.toFixed(2)}</p>
        {stock < 10 && <span>Low stock!</span>}
      </div>
    ))}
  </div>
);

// Multiple elements with fragment
const Timeline = ({ events }) => (
  <div>
    {events.map(event => (
      <React.Fragment key={event.id}>
        <h3>{event.title}</h3>
        <p>{event.description}</p>
        <time>{event.date}</time>
        <hr />
      </React.Fragment>
    ))}
  </div>
);

// Complex logic with block
const PostList = ({ posts }) => (
  <div>
    {posts.map((post) => {
      const isNew = Date.now() - post.timestamp < 86400000;
      const authorName = post.author.name || 'Anonymous';
      
      return (
        <article key={post.id} className={isNew ? 'new' : ''}>
          <h2>{post.title}</h2>
          <p>By {authorName}</p>
          <p>{post.content}</p>
        </article>
      );
    })}
  </div>
);

// Nested arrays
const Categories = ({ categories }) => (
  <div>
    {categories.map(category => (
      <div key={category.id}>
        <h2>{category.name}</h2>
        <ul>
          {category.items.map(item => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      </div>
    ))}
  </div>
);
Note: Always return JSX from map() - common mistake is forgetting the return statement in block syntax. Use {} for expressions, not statements.

2. Key Prop Best Practices and Performance

Key Type Example Stability When to Use
Unique ID BEST key={item.id} Stable across renders Items from database/API
Compound key key={\`\${cat}-\${id}\`} Stable if parts stable Nested lists, multiple sources
Content hash key={hash(item)} Stable for same content Static data, computed keys
Index AVOID key={index} Unstable on reorder ONLY static immutable lists
Random/UUID NEVER key={Math.random()} Never stable Never - causes re-mount
Key Rule Description Impact if Violated
Unique among siblings Keys must be unique within array Incorrect element updates, bugs
Stable across renders Same item = same key Unnecessary re-mounts, state loss
Not globally unique Only unique in local array No issue - scoped to parent
No array index for dynamic Avoid index if items move/delete Wrong items update, state mismatch
Don't use objects Keys must be string/number Warning, uses toString()

Example: Key prop patterns and anti-patterns

// ✅ GOOD: Stable unique IDs from data
const GoodList = ({ items }) => (
  <ul>
    {items.map(item => (
      <li key={item.id}>{item.name}</li>
    ))}
  </ul>
);

// ✅ GOOD: Compound keys for nested structures
const NestedGood = ({ categories }) => (
  <div>
    {categories.map(cat => (
      <div key={cat.id}>
        <h2>{cat.name}</h2>
        {cat.products.map(prod => (
          <div key={\`\${cat.id}-\${prod.id}\`}>
            {prod.name}
          </div>
        ))}
      </div>
    ))}
  </div>
);

// ⚠️ ACCEPTABLE: Index only for static lists
const StaticList = () => {
  const staticItems = ['Home', 'About', 'Contact']; // Never changes
  
  return (
    <ul>
      {staticItems.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
};

// ❌ BAD: Index with dynamic operations
const BadDynamic = ({ items, onDelete }) => (
  <ul>
    {items.map((item, index) => (
      <li key={index}> {/* BUG: Keys shift on delete */}
        <input defaultValue={item.name} />
        <button onClick={() => onDelete(index)}>Delete</button>
      </li>
    ))}
  </ul>
);

// ❌ BAD: Random/changing keys
const BadRandom = ({ items }) => (
  <ul>
    {items.map(item => (
      <li key={Math.random()}> {/* Always re-mounts */}
        {item.name}
      </li>
    ))}
  </ul>
);

// ❌ BAD: Non-unique keys
const BadDuplicate = ({ items }) => (
  <ul>
    {items.map(item => (
      <li key={item.type}> {/* Multiple items same type */}
        {item.name}
      </li>
    ))}
  </ul>
);

// ✅ GOOD: Generate stable ID if missing
const WithGeneratedID = ({ items }) => {
  const itemsWithIds = useMemo(() => 
    items.map((item, index) => ({
      ...item,
      _key: item.id || \`generated-\${index}-\${item.name}\`
    })),
    [items]
  );
  
  return (
    <ul>
      {itemsWithIds.map(item => (
        <li key={item._key}>{item.name}</li>
      ))}
    </ul>
  );
};
Warning: Using array index as key with reorderable/deletable lists causes bugs: component state (like input values) gets assigned to wrong items after operations.

3. Dynamic List Updates and State Management

Operation Pattern Immutability
Add to end [...items, newItem] Creates new array
Add to start [newItem, ...items] Creates new array
Insert at index [...items.slice(0, i), item, ...items.slice(i)] New array with insertion
Remove by index items.filter((_, i) => i !== index) New array without item
Remove by ID items.filter(i => i.id !== id) New array without item
Update by index items.map((i, idx) => idx === index ? {...i, prop} : i) New array with updated item
Update by ID items.map(i => i.id === id ? {...i, prop} : i) New array with updated item
Replace all setItems(newItems) Complete replacement
Sort [...items].sort(compareFn) Copy then sort
Reverse [...items].reverse() Copy then reverse

Example: Dynamic list operations with immutable state

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');
  
  // Add new todo
  const addTodo = () => {
    if (input.trim()) {
      setTodos([...todos, {
        id: Date.now(),
        text: input,
        completed: false
      }]);
      setInput('');
    }
  };
  
  // Delete todo by ID
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  
  // Toggle completed status
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id
        ? { ...todo, completed: !todo.completed }
        : todo
    ));
  };
  
  // Update todo text
  const updateTodo = (id, newText) => {
    setTodos(todos.map(todo =>
      todo.id === id
        ? { ...todo, text: newText }
        : todo
    ));
  };
  
  // Move todo up
  const moveUp = (index) => {
    if (index > 0) {
      const newTodos = [...todos];
      [newTodos[index - 1], newTodos[index]] = 
        [newTodos[index], newTodos[index - 1]];
      setTodos(newTodos);
    }
  };
  
  // Clear completed
  const clearCompleted = () => {
    setTodos(todos.filter(todo => !todo.completed));
  };
  
  return (
    <div>
      <input
        value={input}
        onChange={e => setInput(e.target.value)}
        onKeyPress={e => e.key === 'Enter' && addTodo()}
      />
      <button onClick={addTodo}>Add</button>
      
      <ul>
        {todos.map((todo, index) => (
          <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={() => moveUp(index)}>↑</button>
            <button onClick={() => deleteTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
      
      <button onClick={clearCompleted}>Clear Completed</button>
    </div>
  );
};

// Advanced: Batch updates with useReducer
const listReducer = (state, action) => {
  switch (action.type) {
    case 'ADD':
      return [...state, action.item];
    
    case 'DELETE':
      return state.filter(item => item.id !== action.id);
    
    case 'UPDATE':
      return state.map(item =>
        item.id === action.id
          ? { ...item, ...action.updates }
          : item
      );
    
    case 'REORDER':
      const result = [...state];
      const [removed] = result.splice(action.from, 1);
      result.splice(action.to, 0, removed);
      return result;
    
    case 'BULK_UPDATE':
      return state.map(item =>
        action.ids.includes(item.id)
          ? { ...item, ...action.updates }
          : item
      );
    
    default:
      return state;
  }
};

const AdvancedList = () => {
  const [items, dispatch] = useReducer(listReducer, []);
  
  return (
    <div>
      <button onClick={() => dispatch({
        type: 'ADD',
        item: { id: Date.now(), text: 'New item' }
      })}>
        Add Item
      </button>
      
      {items.map(item => (
        <div key={item.id}>
          {item.text}
          <button onClick={() => dispatch({
            type: 'DELETE',
            id: item.id
          })}>
            Delete
          </button>
        </div>
      ))}
    </div>
  );
};
Note: Never mutate state arrays directly (items.push(), items[0] = x). Always create new arrays to trigger React re-renders properly.

4. Conditional Rendering and Null Patterns

Pattern Syntax Renders Use Case
Logical AND {condition && <Component />} Component or nothing Simple show/hide
Ternary {condition ? <A /> : <B />} Either A or B Two alternatives
Null coalescing {value ?? 'default'} Value or default Null/undefined fallback
Optional chaining {obj?.prop?.value} Value or undefined Safe nested access
Array filter {arr.filter(cond).map(...)} Filtered items Conditional lists
Early return if (!data) return null; Nothing or rest Loading/error states
Empty fragment <>{condition && ...}</> Conditional children Multiple conditional elements
Nullish rendering {value || 'N/A'} Value or fallback Display defaults

Example: Conditional rendering patterns

// Logical AND for simple conditions
const Greeting = ({ isLoggedIn, username }) => (
  <div>
    <h1>Welcome</h1>
    {isLoggedIn && <p>Hello, {username}!</p>}
  </div>
);

// Ternary for either/or
const Status = ({ isOnline }) => (
  <div>
    {isOnline ? (
      <span className="online">Online</span>
    ) : (
      <span className="offline">Offline</span>
    )}
  </div>
);

// Early returns for complex conditions
const UserProfile = ({ user, isLoading, error }) => {
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return <div>No user found</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
};

// Nested conditionals with optional chaining
const Address = ({ user }) => (
  <div>
    <p>{user?.address?.street ?? 'No address provided'}</p>
    <p>{user?.address?.city}, {user?.address?.country}</p>
  </div>
);

// Multiple conditions in list
const ItemList = ({ items, showOnlyActive, sortByName }) => {
  const filteredItems = items
    .filter(item => !showOnlyActive || item.active)
    .sort((a, b) => sortByName ? a.name.localeCompare(b.name) : 0);
  
  return (
    <ul>
      {filteredItems.length === 0 ? (
        <li>No items to display</li>
      ) : (
        filteredItems.map(item => (
          <li key={item.id}>
            {item.name}
            {item.isNew && <span className="badge">NEW</span>}
          </li>
        ))
      )}
    </ul>
  );
};

// Complex multi-condition rendering
const Dashboard = ({ user, permissions, settings }) => (
  <div>
    {/* Admin section */}
    {user.role === 'admin' && (
      <section>
        <h2>Admin Panel</h2>
        {permissions.canEdit && <button>Edit</button>}
        {permissions.canDelete && <button>Delete</button>}
      </section>
    )}
    
    {/* Premium features */}
    {user.isPremium ? (
      <PremiumFeatures />
    ) : (
      <div>
        <p>Upgrade to Premium</p>
        <button>Upgrade Now</button>
      </div>
    )}
    
    {/* Settings based on flags */}
    {settings.showNotifications && <NotificationPanel />}
    {settings.enableDarkMode && <ThemeToggle />}
  </div>
);

// Avoiding falsy value bugs
const Counter = ({ count }) => (
  <div>
    {/* ❌ BAD: renders "0" when count is 0 */}
    {count && <p>Count: {count}</p>}
    
    {/* ✅ GOOD: explicit check */}
    {count > 0 && <p>Count: {count}</p>}
    
    {/* ✅ GOOD: ternary for all cases */}
    {count ? <p>Count: {count}</p> : <p>No items</p>}
  </div>
);
Warning: Watch out for falsy values in logical AND: 0 && <Component /> renders "0", not nothing. Use explicit boolean checks or ternary operators.

5. List Filtering and Search Implementation

Technique Method Performance Use Case
Simple filter items.filter(predicate) O(n) Small lists, simple conditions
Case-insensitive search toLowerCase().includes() O(n×m) Text search
Multiple criteria filter(i => cond1 && cond2) O(n) Advanced filtering
Debounced search Delay filter execution Reduced calls Live search, API calls
Memoized filter useMemo(() => filter) Cached result Expensive filters, large lists
Index search Pre-built lookup maps O(1) lookup Very large datasets

Example: Search and filter implementations

// Basic search filter
const SearchList = () => {
  const [items] = useState([
    { id: 1, name: 'Apple', category: 'Fruit' },
    { id: 2, name: 'Banana', category: 'Fruit' },
    { id: 3, name: 'Carrot', category: 'Vegetable' }
  ]);
  const [search, setSearch] = useState('');
  
  const filteredItems = items.filter(item =>
    item.name.toLowerCase().includes(search.toLowerCase())
  );
  
  return (
    <div>
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
        placeholder="Search..."
      />
      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

// Multi-criteria filter with memoization
const AdvancedFilter = () => {
  const [products, setProducts] = useState([]);
  const [filters, setFilters] = useState({
    search: '',
    category: 'all',
    minPrice: 0,
    maxPrice: 1000,
    inStock: false
  });
  
  const filteredProducts = useMemo(() => {
    return products.filter(product => {
      // Search filter
      if (filters.search && 
          !product.name.toLowerCase().includes(filters.search.toLowerCase())) {
        return false;
      }
      
      // Category filter
      if (filters.category !== 'all' && product.category !== filters.category) {
        return false;
      }
      
      // Price range
      if (product.price < filters.minPrice || product.price > filters.maxPrice) {
        return false;
      }
      
      // Stock filter
      if (filters.inStock && product.stock === 0) {
        return false;
      }
      
      return true;
    });
  }, [products, filters]);
  
  return (
    <div>
      <input
        value={filters.search}
        onChange={e => setFilters({...filters, search: e.target.value})}
        placeholder="Search products..."
      />
      
      <select
        value={filters.category}
        onChange={e => setFilters({...filters, category: e.target.value})}
      >
        <option value="all">All Categories</option>
        <option value="electronics">Electronics</option>
        <option value="clothing">Clothing</option>
      </select>
      
      <label>
        <input
          type="checkbox"
          checked={filters.inStock}
          onChange={e => setFilters({...filters, inStock: e.target.checked})}
        />
        In Stock Only
      </label>
      
      <p>Found {filteredProducts.length} products</p>
      
      <div>
        {filteredProducts.map(product => (
          <div key={product.id}>
            <h3>{product.name}</h3>
            <p>${product.price}</p>
            <p>Stock: {product.stock}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

// Debounced search for better performance
const useDebounce = (value, delay) => {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => clearTimeout(handler);
  }, [value, delay]);
  
  return debouncedValue;
};

const DebouncedSearch = () => {
  const [items] = useState([/* large array */]);
  const [search, setSearch] = useState('');
  const debouncedSearch = useDebounce(search, 300);
  
  const filteredItems = useMemo(() => {
    if (!debouncedSearch) return items;
    
    return items.filter(item =>
      item.name.toLowerCase().includes(debouncedSearch.toLowerCase())
    );
  }, [items, debouncedSearch]);
  
  return (
    <div>
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
        placeholder="Search (debounced)..."
      />
      <p>Searching for: {debouncedSearch}</p>
      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

// Sorting combined with filtering
const SortableFilterable = () => {
  const [items, setItems] = useState([]);
  const [search, setSearch] = useState('');
  const [sortBy, setSortBy] = useState('name');
  const [sortOrder, setSortOrder] = useState('asc');
  
  const processedItems = useMemo(() => {
    // First filter
    let result = items.filter(item =>
      item.name.toLowerCase().includes(search.toLowerCase())
    );
    
    // Then sort
    result.sort((a, b) => {
      const aVal = a[sortBy];
      const bVal = b[sortBy];
      
      if (typeof aVal === 'string') {
        return sortOrder === 'asc'
          ? aVal.localeCompare(bVal)
          : bVal.localeCompare(aVal);
      }
      
      return sortOrder === 'asc'
        ? aVal - bVal
        : bVal - aVal;
    });
    
    return result;
  }, [items, search, sortBy, sortOrder]);
  
  return (
    <div>
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
      />
      
      <select value={sortBy} onChange={e => setSortBy(e.target.value)}>
        <option value="name">Name</option>
        <option value="price">Price</option>
        <option value="date">Date</option>
      </select>
      
      <button onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}>
        {sortOrder === 'asc' ? '↑' : '↓'}
      </button>
      
      <ul>
        {processedItems.map(item => (
          <li key={item.id}>{item.name} - ${item.price}</li>
        ))}
      </ul>
    </div>
  );
};
Note: Use useMemo for expensive filter/sort operations on large lists. Add debouncing for search inputs to reduce unnecessary re-renders.

6. Virtualized Lists and Large Dataset Handling

Technique Description Best For Library
Windowing Render only visible items Long uniform lists react-window
Virtual scrolling Dynamic item height handling Variable height items react-virtualized
Infinite scroll Load more on scroll bottom Paginated data react-infinite-scroll
Pagination Page-based navigation Discrete data chunks Custom implementation
Lazy rendering Render on intersection Complex card layouts IntersectionObserver
Cursor-based Load by cursor position Real-time feeds API + state management

Example: Virtualized list with react-window

import { FixedSizeList } from 'react-window';

// Basic virtualized list
const VirtualList = ({ items }) => {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );
  
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
};

// Variable height list
import { VariableSizeList } from 'react-window';

const VariableHeightList = ({ items }) => {
  const listRef = useRef();
  
  const getItemSize = (index) => {
    // Calculate height based on content
    return items[index].description.length > 100 ? 120 : 60;
  };
  
  const Row = ({ index, style }) => (
    <div style={style}>
      <h4>{items[index].title}</h4>
      <p>{items[index].description}</p>
    </div>
  );
  
  return (
    <VariableSizeList
      ref={listRef}
      height={600}
      itemCount={items.length}
      itemSize={getItemSize}
      width="100%"
    >
      {Row}
    </VariableSizeList>
  );
};

// Infinite scroll implementation
const InfiniteScrollList = () => {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);
  const observerRef = useRef();
  
  const lastItemRef = useCallback((node) => {
    if (loading) return;
    
    if (observerRef.current) observerRef.current.disconnect();
    
    observerRef.current = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting && hasMore) {
        setPage(prev => prev + 1);
      }
    });
    
    if (node) observerRef.current.observe(node);
  }, [loading, hasMore]);
  
  useEffect(() => {
    const fetchMore = async () => {
      setLoading(true);
      const response = await fetch(\`/api/items?page=\${page}\`);
      const newItems = await response.json();
      
      setItems(prev => [...prev, ...newItems]);
      setHasMore(newItems.length > 0);
      setLoading(false);
    };
    
    fetchMore();
  }, [page]);
  
  return (
    <div>
      {items.map((item, index) => {
        if (items.length === index + 1) {
          return (
            <div ref={lastItemRef} key={item.id}>
              {item.name}
            </div>
          );
        }
        return <div key={item.id}>{item.name}</div>;
      })}
      {loading && <div>Loading...</div>}
    </div>
  );
};

// Pagination implementation
const PaginatedList = () => {
  const [items, setItems] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [totalPages, setTotalPages] = useState(1);
  const itemsPerPage = 20;
  
  useEffect(() => {
    const fetchPage = async () => {
      const response = await fetch(
        \`/api/items?page=\${currentPage}&limit=\${itemsPerPage}\`
      );
      const data = await response.json();
      
      setItems(data.items);
      setTotalPages(Math.ceil(data.total / itemsPerPage));
    };
    
    fetchPage();
  }, [currentPage]);
  
  return (
    <div>
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      
      <div className="pagination">
        <button
          onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
          disabled={currentPage === 1}
        >
          Previous
        </button>
        
        <span>Page {currentPage} of {totalPages}</span>
        
        <button
          onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
          disabled={currentPage === totalPages}
        >
          Next
        </button>
      </div>
    </div>
  );
};

// Lazy loading with intersection observer
const LazyList = ({ items }) => {
  const [visibleItems, setVisibleItems] = useState(new Set());
  
  const observeItem = useCallback((node, id) => {
    if (!node) return;
    
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            setVisibleItems(prev => new Set([...prev, id]));
            observer.disconnect();
          }
        });
      },
      { rootMargin: '100px' }
    );
    
    observer.observe(node);
  }, []);
  
  return (
    <div>
      {items.map(item => (
        <div
          key={item.id}
          ref={node => observeItem(node, item.id)}
          style={{ minHeight: '100px' }}
        >
          {visibleItems.has(item.id) ? (
            <ExpensiveComponent item={item} />
          ) : (
            <div>Loading...</div>
          )}
        </div>
      ))}
    </div>
  );
};
Performance Tips:
  • Use virtualization for lists > 100 items
  • Implement infinite scroll for continuous feeds
  • Prefer pagination for searchable/filterable data
  • Use IntersectionObserver for lazy loading images/components
  • Memoize row components with React.memo

Lists and Rendering Best Practices