Code To Learn logo

Code To Learn

M2: State & Events

L7: M2 Challenge & Wrap-up

Apply your skills with a comprehensive challenge

Challenge Practice

Put everything you've learned together in this comprehensive challenge! You'll enhance StaySense with additional features using state management, event handling, and filtering techniques.


šŸŽÆ Challenge Overview

Enhance your StaySense application with these features:

Required Features

  1. "Save for Later" Functionality

    • Add a heart icon to each listing card
    • Click to toggle saved status
    • Track saved listings in state
    • Show count of saved listings
  2. Advanced Filtering

    • Add price range filter (min/max)
    • Add property type filter (house/cabin/loft/all)
    • Combine all filters seamlessly
  3. Sort Options

    • Sort by price (low to high, high to low)
    • Sort by guest capacity
    • Sort by name (A-Z)

Bonus Features (Optional)

  1. Filter Summary

    • Show active filters as removable chips
    • Click chip to remove individual filter
  2. View Saved Listings

    • Toggle between "All Listings" and "Saved Listings"
    • Filter works on saved view too

šŸ“‹ Requirements Checklist

Before you begin, make sure you have:

  • āœ… Completed Lessons 1-6
  • āœ… Working filter system
  • āœ… Understanding of state management
  • āœ… Familiarity with array methods

šŸš€ Challenge Instructions

Part 1: Save for Later Feature

Goal: Let users save their favorite listings.

Steps:

  1. Add savedListings state to HomePage (array of IDs):
const [savedListings, setSavedListings] = useState([]);
  1. Create toggle function:
const toggleSaved = (listingId) => {
  setSavedListings(prev => {
    if (prev.includes(listingId)) {
      return prev.filter(id => id !== listingId);
    } else {
      return [...prev, listingId];
    }
  });
};
  1. Pass to PropertyCard:
<PropertyCard 
  listing={listing}
  isSaved={savedListings.includes(listing.id)}
  onToggleSave={toggleSaved}
/>
  1. Update PropertyCard with heart button:
export function PropertyCard({ listing, isSaved, onToggleSave }) {
  return (
    <div className="card">
      <button 
        onClick={() => onToggleSave(listing.id)}
        className="absolute top-2 right-2 z-10"
      >
        {isSaved ? 'ā¤ļø' : 'šŸ¤'}
      </button>
      {/* Rest of card... */}
    </div>
  );
}

Test: Click hearts to save/unsave listings.

Part 2: Price Range Filter

Goal: Filter by minimum and maximum price.

Steps:

  1. Add price state to HomePage:
const [minPrice, setMinPrice] = useState(0);
const [maxPrice, setMaxPrice] = useState(1000);
  1. Add price inputs to ListingFilters:
<div className="grid grid-cols-2 gap-4 mb-4">
  <div>
    <label>Min Price</label>
    <input 
      type="number" 
      value={minPrice}
      onChange={(e) => onMinPriceChange(Number(e.target.value))}
      min="0"
    />
  </div>
  <div>
    <label>Max Price</label>
    <input 
      type="number" 
      value={maxPrice}
      onChange={(e) => onMaxPriceChange(Number(e.target.value))}
      min="0"
    />
  </div>
</div>
  1. Update filtering logic:
const matchesPrice = 
  listing.price >= minPrice && 
  listing.price <= maxPrice;

Test: Adjust price range to filter listings.

Part 3: Property Type Filter

Goal: Filter by property type.

Steps:

  1. Add property type to listings data:
{
  id: 1,
  title: "Cozy Beach House",
  type: "house", // Add this
  // ... rest
}
  1. Add type state:
const [propertyType, setPropertyType] = useState('all');
  1. Add select dropdown to ListingFilters:
<div className="mb-4">
  <label>Property Type</label>
  <select 
    value={propertyType}
    onChange={(e) => onPropertyTypeChange(e.target.value)}
    className="w-full px-4 py-2 border rounded-lg"
  >
    <option value="all">All Types</option>
    <option value="house">House</option>
    <option value="cabin">Cabin</option>
    <option value="loft">Loft</option>
  </select>
</div>
  1. Update filter logic:
const matchesType = 
  propertyType === 'all' || 
  listing.type === propertyType;

Test: Select different property types.

Part 4: Sort Functionality

Goal: Allow users to sort filtered results.

Steps:

  1. Add sort state:
const [sortBy, setSortBy] = useState('default');
  1. Create sort function:
const getSortedListings = (listings) => {
  const sorted = [...listings];
  
  switch(sortBy) {
    case 'price-low':
      return sorted.sort((a, b) => a.price - b.price);
    case 'price-high':
      return sorted.sort((a, b) => b.price - a.price);
    case 'guests':
      return sorted.sort((a, b) => b.guests - a.guests);
    case 'name':
      return sorted.sort((a, b) => a.title.localeCompare(b.title));
    default:
      return sorted;
  }
};
  1. Apply sorting after filtering:
const filteredListings = getFilteredListings();
const sortedListings = getSortedListings(filteredListings);
  1. Add sort dropdown to filters:
<div className="mb-4">
  <label>Sort By</label>
  <select 
    value={sortBy}
    onChange={(e) => setSortBy(e.target.value)}
    className="w-full px-4 py-2 border rounded-lg"
  >
    <option value="default">Default</option>
    <option value="price-low">Price: Low to High</option>
    <option value="price-high">Price: High to Low</option>
    <option value="guests">Guest Capacity</option>
    <option value="name">Name (A-Z)</option>
  </select>
</div>

Test: Try different sort options.

Part 5: View Saved Listings (Bonus)

Goal: Toggle between all listings and saved only.

Steps:

  1. Add view state:
const [view, setView] = useState('all'); // 'all' or 'saved'
  1. Add toggle buttons:
<div className="flex gap-2 mb-4">
  <button 
    onClick={() => setView('all')}
    className={view === 'all' ? 'active' : ''}
  >
    All Listings ({listings.length})
  </button>
  <button 
    onClick={() => setView('saved')}
    className={view === 'saved' ? 'active' : ''}
  >
    Saved ({savedListings.length})
  </button>
</div>
  1. Filter by view:
const getViewListings = () => {
  if (view === 'saved') {
    return listings.filter(l => savedListings.includes(l.id));
  }
  return listings;
};

const viewListings = getViewListings();
const filteredListings = viewListings.filter(/* filter logic */);

Test: Save some listings, switch to saved view.

Part 6: Filter Chips (Bonus)

Goal: Show active filters as removable chips.

Steps:

  1. Create ActiveFilters component:
function ActiveFilters({ filters, onRemove }) {
  return (
    <div className="flex flex-wrap gap-2 mb-4">
      {filters.map(filter => (
        <div 
          key={filter.id}
          className="bg-blue-100 px-3 py-1 rounded-full flex items-center gap-2"
        >
          <span>{filter.label}</span>
          <button onClick={() => onRemove(filter.id)}>Ɨ</button>
        </div>
      ))}
    </div>
  );
}
  1. Build active filters array:
const getActiveFilters = () => {
  const filters = [];
  
  if (search) {
    filters.push({ id: 'search', label: `Search: ${search}` });
  }
  if (guests > 1) {
    filters.push({ id: 'guests', label: `${guests} guests` });
  }
  if (propertyType !== 'all') {
    filters.push({ id: 'type', label: `Type: ${propertyType}` });
  }
  
  return filters;
};
  1. Implement remove handler:
const handleRemoveFilter = (filterId) => {
  switch(filterId) {
    case 'search':
      setSearch('');
      break;
    case 'guests':
      setGuests(1);
      break;
    case 'type':
      setPropertyType('all');
      break;
  }
};

Test: Apply filters, remove via chips.


āœ… Challenge Completion Checklist

Required Features:

Bonus Features:

Code Quality:


šŸ’” Hints & Tips


šŸŽÆ Solution Preview


šŸŽ‰ Congratulations!

By completing this challenge, you've demonstrated mastery of:

  • āœ… Complex state management
  • āœ… Multiple filter coordination
  • āœ… Sorting algorithms
  • āœ… Array manipulation
  • āœ… Component communication
  • āœ… User experience patterns

You're Ready for Module 3!

In Module 3, you'll learn:

  • useEffect hook for side effects
  • Fetching data from APIs
  • Handling async operations
  • Managing loading and error states
  • Building advanced components

Take a break and celebrate your progress! šŸŽŠ

When you're ready, continue to Module 3: Effects & Data Fetching.