Code To Learn logo

Code To Learn

M2: State & Events

L5: Filtering Listings Dynamically

Implement real-time filtering logic using array methods and state

Array Methods Filtering Logic Real-time Updates

Now that HomePage has access to all filter values, it's time to implement the filtering logic! We'll filter listings in real-time as users interact with the filters.


The Goal

Filter listings based on:

  1. Search - Match location or title
  2. Dates - Check availability (we'll simulate this)
  3. Guests - Must accommodate guest count
Filter Flow
All Listings (3) 

Filter by Search 

Filter by Guests

Filtered Results (1-3)

Understanding Array.filter()

The filter() method creates a new array with elements that pass a test:

Array Filter Basics
const numbers = [1, 2, 3, 4, 5];

// Keep only even numbers
const evens = numbers.filter(num => num % 2 === 0);
// Result: [2, 4]

// Keep numbers greater than 3
const large = numbers.filter(num => num > 3);
// Result: [4, 5]

// Chain multiple conditions
const filtered = numbers.filter(num => num > 2 && num < 5);
// Result: [3, 4]

Key Points:

  • Returns a new array (doesn't modify original)
  • Callback must return true (keep) or false (remove)
  • If all items fail, returns empty array []

Step-by-Step Implementation

Step 1: Create the Filtering Function

Add a function in HomePage to filter listings:

src/pages/HomePage.jsx
export function HomePage() {
  const [listings, setListings] = useState([/* ... */]);
  const [search, setSearch] = useState('');
  const [checkIn, setCheckIn] = useState('');
  const [checkOut, setCheckOut] = useState('');
  const [guests, setGuests] = useState(1);
  
  // Filtering function
  const getFilteredListings = () => { 
    return listings.filter(listing => { 
      // We'll add conditions here
      return true; 
    }); 
  }; 
  
  return (
    // ... JSX
  );
}

Starting Simple: Returns all listings for now (always true).

Step 2: Add Search Filter Logic

Filter by search term (location or title):

src/pages/HomePage.jsx
const getFilteredListings = () => {
  return listings.filter(listing => {
    // Search filter
    const matchesSearch =
      search === '' || // If search is empty, match all
      listing.location.toLowerCase().includes(search.toLowerCase()) ||
      listing.title.toLowerCase().includes(search.toLowerCase()); 
    
    return matchesSearch; 
  });
};

Case-Insensitive: We use .toLowerCase() so "malibu" matches "Malibu".

Partial Matches: includes() finds "Mal" in "Malibu".

Step 3: Add Guest Count Filter

Filter by number of guests:

src/pages/HomePage.jsx
const getFilteredListings = () => {
  return listings.filter(listing => {
    // Search filter
    const matchesSearch =
      search === '' ||
      listing.location.toLowerCase().includes(search.toLowerCase()) ||
      listing.title.toLowerCase().includes(search.toLowerCase());
    
    // Guest filter
    const matchesGuests = listing.guests >= guests; 
    
    return matchesSearch && matchesGuests; 
  });
};

Logic: Listing must accommodate at least the requested number of guests.

Step 4: Add Date Filter (Simulated)

Add basic date validation:

src/pages/HomePage.jsx
const getFilteredListings = () => {
  return listings.filter(listing => {
    // Search filter
    const matchesSearch =
      search === '' ||
      listing.location.toLowerCase().includes(search.toLowerCase()) ||
      listing.title.toLowerCase().includes(search.toLowerCase());
    
    // Guest filter
    const matchesGuests = listing.guests >= guests;
    
    // Date filter (basic validation)
    const matchesDates =
      checkIn === '' || // If no date selected, match all
      checkOut === '' || // If no date selected, match all
      (checkIn && checkOut && checkIn < checkOut); // Validate date range
    
    return matchesSearch && matchesGuests && matchesDates; 
  });
};

Note: In a real app, you'd check actual availability against a calendar. Here we just validate the date range makes sense.

Step 5: Use Filtered Listings in Render

Replace listings with getFilteredListings() in the JSX:

src/pages/HomePage.jsx
export function HomePage() {
  // ... state and getFilteredListings function
  
  const filteredListings = getFilteredListings(); 
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-8">Available Stays</h1>
      
      <ListingFilters
        search={search}
        checkIn={checkIn}
        checkOut={checkOut}
        guests={guests}
        onSearchChange={setSearch}
        onCheckInChange={setCheckIn}
        onCheckOutChange={setCheckOut}
        onGuestsChange={setGuests}
      />
      
      {/* Show results count */}
      <p className="text-gray-600 mb-4"> // [!code ++]
        Found {filteredListings.length} {filteredListings.length === 1 ? 'listing' : 'listings'} // [!code ++]
      </p> // [!code ++]
      
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {filteredListings.map(listing => ( 
          <PropertyCard key={listing.id} listing={listing} />
        ))}
      </div>
    </div>
  );
}

Real-time Filtering: As state updates, getFilteredListings() runs again, filtering the list dynamically.

Step 6: Add Empty State

Show a message when no listings match:

src/pages/HomePage.jsx
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  {filteredListings.length === 0 ? ( 
    <div className="col-span-full text-center py-12"> // [!code ++]
      <p className="text-gray-500 text-lg mb-2">No listings found</p> // [!code ++]
      <p className="text-gray-400">Try adjusting your filters</p> // [!code ++]
    </div> 
  ) : ( 
    filteredListings.map(listing => (
      <PropertyCard key={listing.id} listing={listing} />
    ))
  )} // [!code ++]
</div>

Better UX: Users know why they see no results.

Step 7: Test the Filters

Try each filter:

Search Test:

  • Type "Malibu" → Should show Beach House
  • Type "cabin" → Should show Mountain Cabin
  • Type "xyz" → Should show empty state

Guest Test:

  • Set guests to 5 → Should show Mountain Cabin (6 guests)
  • Set guests to 1 → Should show all listings

Combined Test:

  • Search "beach" + 2 guests → Should show Beach House
  • Search "cabin" + 7 guests → Should show empty state (cabin only fits 6)

Real-time Updates! No page refresh needed - everything updates instantly.


Complete Filtering Code

Here's the complete HomePage with all filtering logic:

src/pages/HomePage.jsx
import { useState } from 'react';
import PropertyCard from '../components/PropertyCard';
import { ListingFilters } from '../components/ListingFilters';

export function HomePage() {
  // Listings state
  const [listings, setListings] = useState([
    {
      id: 1,
      title: "Cozy Beach House",
      price: 250,
      location: "Malibu, CA",
      image: "/images/beach-house.jpg",
      guests: 4,
      bedrooms: 2,
      bathrooms: 2,
    },
    {
      id: 2,
      title: "Mountain Cabin",
      price: 180,
      location: "Aspen, CO",
      image: "/images/cabin.jpg",
      guests: 6,
      bedrooms: 3,
      bathrooms: 2,
    },
    {
      id: 3,
      title: "Downtown Loft",
      price: 320,
      location: "New York, NY",
      image: "/images/loft.jpg",
      guests: 2,
      bedrooms: 1,
      bathrooms: 1,
    },
  ]);
  
  // Filter state
  const [search, setSearch] = useState('');
  const [checkIn, setCheckIn] = useState('');
  const [checkOut, setCheckOut] = useState('');
  const [guests, setGuests] = useState(1);
  
  // Filtering function
  const getFilteredListings = () => { 
    return listings.filter(listing => {
      // Search filter
      const matchesSearch =
        search === '' ||
        listing.location.toLowerCase().includes(search.toLowerCase()) ||
        listing.title.toLowerCase().includes(search.toLowerCase());
      
      // Guest filter
      const matchesGuests = listing.guests >= guests;
      
      // Date filter (validation only)
      const matchesDates =
        checkIn === '' ||
        checkOut === '' ||
        (checkIn && checkOut && checkIn < checkOut);
      
      return matchesSearch && matchesGuests && matchesDates;
    });
  }; 
  
  const filteredListings = getFilteredListings(); 
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-8">Available Stays</h1>
      
      <ListingFilters
        search={search}
        checkIn={checkIn}
        checkOut={checkOut}
        guests={guests}
        onSearchChange={setSearch}
        onCheckInChange={setCheckIn}
        onCheckOutChange={setCheckOut}
        onGuestsChange={setGuests}
      />
      
      <p className="text-gray-600 mb-4">
        Found {filteredListings.length} {filteredListings.length === 1 ? 'listing' : 'listings'}
      </p>
      
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {filteredListings.length === 0 ? (
          <div className="col-span-full text-center py-12">
            <p className="text-gray-500 text-lg mb-2">No listings found</p>
            <p className="text-gray-400">Try adjusting your filters</p>
          </div>
        ) : (
          filteredListings.map(listing => (
            <PropertyCard key={listing.id} listing={listing} />
          ))
        )}
      </div>
    </div>
  );
}

Advanced Filtering Patterns

Pattern 1: Extract Filter Functions

For better organization, separate each filter:

const matchesSearch = (listing, search) => {
  if (search === '') return true;
  
  const searchLower = search.toLowerCase();
  return (
    listing.location.toLowerCase().includes(searchLower) ||
    listing.title.toLowerCase().includes(searchLower)
  );
};

const matchesGuests = (listing, guests) => {
  return listing.guests >= guests;
};

const getFilteredListings = () => {
  return listings.filter(listing => {
    return (
      matchesSearch(listing, search) &&
      matchesGuests(listing, guests)
    );
  });
};

Pattern 2: Filter with Reduce (Advanced)

const getFilteredListings = () => {
  return listings.reduce((acc, listing) => {
    if (
      matchesSearch(listing) &&
      matchesGuests(listing) &&
      matchesDates()
    ) {
      acc.push(listing);
    }
    return acc;
  }, []);
};

Pattern 3: Multiple Filters with Array

const filters = [
  (listing) => matchesSearch(listing, search),
  (listing) => matchesGuests(listing, guests),
  (listing) => matchesDates(checkIn, checkOut),
];

const getFilteredListings = () => {
  return listings.filter(listing => {
    return filters.every(filterFn => filterFn(listing));
  });
};

Performance Optimization

Use useMemo for Expensive Filtering

import { useState, useMemo } from 'react';

export function HomePage() {
  // ... state
  
  const filteredListings = useMemo(() => { 
    return listings.filter(listing => {
      // ... filtering logic
    });
  }, [listings, search, guests, checkIn, checkOut]); 
  
  // filteredListings only recalculates when dependencies change
}

When to use: With large datasets (100+ items) or complex filtering logic.


Filtering Edge Cases

Case 1: Trimming Whitespace

const matchesSearch =
  search.trim() === '' ||
  listing.location.toLowerCase().includes(search.trim().toLowerCase());

Case 2: Multiple Search Terms

const searchTerms = search.toLowerCase().split(' ').filter(term => term);

const matchesSearch = searchTerms.every(term =>
  listing.location.toLowerCase().includes(term) ||
  listing.title.toLowerCase().includes(term)
);

Case 3: Price Range Filter (Bonus)

const [minPrice, setMinPrice] = useState(0);
const [maxPrice, setMaxPrice] = useState(1000);

const matchesPrice = 
  listing.price >= minPrice && 
  listing.price <= maxPrice;

Checkpoint


What's Next?

Congratulations! You've built a fully functional filtering system with:

  • ✅ Multiple filter controls
  • ✅ Real-time updates
  • ✅ State management
  • ✅ Component communication

In Lesson 6, we'll wrap up Module 2 with:

  • Module review and key concepts
  • Best practices recap
  • Common patterns summary
  • Preview of Module 3 (Effects & Data Fetching)

You've mastered React state and interactivity! 🎉