Skip to main content

Exercise 3: Dynamic List Rendering

Objective

Master list rendering, keys, filtering, sorting, and CRUD operations.

Task

Build a product management dashboard with full CRUD functionality.

Requirements

1. Product Interface

interface Product {
id: string;
name: string;
category: 'electronics' | 'clothing' | 'food' | 'books';
price: number;
stock: number;
rating: number;
createdAt: Date;
}

2. Features

Display

  • Grid view of product cards
  • Show all product details
  • Display stock status (In Stock / Low Stock / Out of Stock)
  • Show rating as stars
  • Format price with currency

Search & Filter

  • Search by product name (real-time)
  • Filter by category
  • Filter by stock status
  • Filter by price range (slider or inputs)
  • Show number of results

Sort

  • Sort by name (A-Z, Z-A)
  • Sort by price (low to high, high to low)
  • Sort by rating
  • Sort by stock level
  • Sort by newest/oldest

CRUD Operations

  • Create: Add new product with form
  • Read: Display all products
  • Update: Edit product details inline or in modal
  • Delete: Remove product with confirmation

3. UI Components

// Main component
function ProductDashboard() {
// State management
// Filter/sort logic
// CRUD handlers
}

// Product card
function ProductCard({ product, onEdit, onDelete }) {
// Display product
// Action buttons
}

// Add/Edit form
function ProductForm({ product, onSave, onCancel }) {
// Form fields
// Validation
// Submit handler
}

// Filters panel
function FiltersPanel({ filters, onFilterChange }) {
// Search input
// Category filter
// Price range
// Stock filter
}

// Sort dropdown
function SortDropdown({ sortBy, onSortChange }) {
// Sort options
}

Sample Data

const sampleProducts: Product[] = [
{
id: '1',
name: 'Wireless Headphones',
category: 'electronics',
price: 89.99,
stock: 45,
rating: 4.5,
createdAt: new Date('2024-01-15'),
},
{
id: '2',
name: 'Cotton T-Shirt',
category: 'clothing',
price: 24.99,
stock: 120,
rating: 4.2,
createdAt: new Date('2024-02-01'),
},
// Add more sample products...
];

Implementation Guidelines

State Management

const [products, setProducts] = useState<Product[]>(sampleProducts);
const [searchTerm, setSearchTerm] = useState('');
const [categoryFilter, setCategoryFilter] = useState<string>('all');
const [sortBy, setSortBy] = useState<string>('name-asc');
const [priceRange, setPriceRange] = useState({ min: 0, max: 1000 });

Filter Logic

const filteredProducts = useMemo(() => {
return products
.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase()))
.filter(p => categoryFilter === 'all' || p.category === categoryFilter)
.filter(p => p.price >= priceRange.min && p.price <= priceRange.max);
}, [products, searchTerm, categoryFilter, priceRange]);

Sort Logic

const sortedProducts = useMemo(() => {
const sorted = [...filteredProducts];

switch (sortBy) {
case 'name-asc':
return sorted.sort((a, b) => a.name.localeCompare(b.name));
case 'name-desc':
return sorted.sort((a, b) => b.name.localeCompare(a.name));
case 'price-asc':
return sorted.sort((a, b) => a.price - b.price);
case 'price-desc':
return sorted.sort((a, b) => b.price - a.price);
// Add more sort cases...
default:
return sorted;
}
}, [filteredProducts, sortBy]);

CRUD Handlers

const addProduct = (product: Omit<Product, 'id' | 'createdAt'>) => {
const newProduct: Product = {
...product,
id: crypto.randomUUID(),
createdAt: new Date(),
};
setProducts(prev => [...prev, newProduct]);
};

const updateProduct = (id: string, updates: Partial<Product>) => {
setProducts(prev => prev.map(p => (p.id === id ? { ...p, ...updates } : p)));
};

const deleteProduct = (id: string) => {
if (confirm('Are you sure you want to delete this product?')) {
setProducts(prev => prev.filter(p => p.id !== id));
}
};

UI Layout

┌─────────────────────────────────────────┐
│ Product Dashboard │
├─────────────────────────────────────────┤
│ Search: [____________] [+ Add Product]│
├───────────┬─────────────────────────────┤
│ Filters │ Sort: [Dropdown ▼] │
│ │ Showing 12 of 50 products │
│ Category ├─────────────────────────────┤
│ □ All │ ┌────┐ ┌────┐ ┌────┐ │
│ □ Electr. │ │ P1 │ │ P2 │ │ P3 │ │
│ □ Cloth. │ └────┘ └────┘ └────┘ │
│ □ Food │ ┌────┐ ┌────┐ ┌────┐ │
│ □ Books │ │ P4 │ │ P5 │ │ P6 │ │
│ │ └────┘ └────┘ └────┘ │
│ Price │ │
│ [$-$$$] │ [Load More] │
│ │ │
│ Stock │ │
│ ○ All │ │
│ ○ In Stock│ │
│ ○ Low │ │
└───────────┴─────────────────────────────┘

Bonus Challenges

  1. Add pagination or infinite scroll
  2. Implement bulk delete
  3. Add export to CSV functionality
  4. Persist data to localStorage
  5. Add product images with upload
  6. Implement undo/redo for delete
  7. Add keyboard shortcuts (Ctrl+K for search, etc.)
  8. Create a table view toggle
  9. Add favorites/wishlist feature
  10. Implement drag-and-drop reordering

Testing Checklist

  • Products render correctly with proper keys
  • Search filters in real-time
  • Category filter works
  • Price range filter works
  • All sort options work correctly
  • Add product form validates correctly
  • Edit product updates correctly
  • Delete confirms and removes product
  • No products found message shows when filtered to 0
  • Stock status displays correctly
  • Rating stars render properly
  • Product count updates correctly

Time Estimate

4-5 hours

Hints

  • Use useMemo for filtered/sorted lists to avoid re-computation
  • Generate stable IDs with crypto.randomUUID()
  • Use proper TypeScript types throughout
  • Break down into small, reusable components
  • Consider using CSS Grid for the product grid layout