Code To Learn logo

Code To Learn

M6: State ManagementRedux Toolkit Path

L9: Favorites Page

Create the FavoritesPage to display favorited listings

Let's create a dedicated page to display all favorited listings!

What We're Building

A FavoritesPage that:

  1. Shows all favorited listings
  2. Handles empty state (no favorites)
  3. Displays count of favorites
  4. Uses existing ListingList component

Step 1: Create the Page File

Create a new file:

touch src/pages/FavoritesPage.jsx

Step 2: Build the Component

Add the basic structure:

src/pages/FavoritesPage.jsx
import { useSelector } from 'react-redux';
import ListingList from '@/components/ListingList';

function FavoritesPage() {
  const items = useSelector((state) => state.listings.items);
  const favoriteIds = useSelector((state) => state.listings.favorites);
  
  // Filter to get favorited listings
  const favoriteListings = items.filter((listing) =>
    favoriteIds.includes(listing.id)
  );
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-6">
        Your Favorites ({favoriteListings.length})
      </h1>
      
      {favoriteListings.length === 0 ? (
        <div className="text-center py-12">
          <p className="text-gray-500 text-lg">
            No favorites yet. Start exploring listings!
          </p>
        </div>
      ) : (
        <ListingList listings={favoriteListings} />
      )}
    </div>
  );
}

export default FavoritesPage;

What's happening here?

Import Dependencies

import { useSelector } from 'react-redux';
import ListingList from '@/components/ListingList';
  • useSelector - Read from Redux store
  • ListingList - Existing component to display listings

Select State

const items = useSelector((state) => state.listings.items);
const favoriteIds = useSelector((state) => state.listings.favorites);

We need both:

  • items - All listings (with full data)
  • favoriteIds - Array of favorited IDs

Filter Favorites

const favoriteListings = items.filter((listing) =>
  favoriteIds.includes(listing.id)
);

How it works:

// items = [
//   { id: 1, title: 'Beach House' },
//   { id: 2, title: 'Mountain Cabin' },
//   { id: 3, title: 'City Apartment' },
//   { id: 4, title: 'Lake House' },
//   { id: 5, title: 'Desert Villa' }
// ]

// favoriteIds = [1, 3, 5]

// favoriteListings = [
//   { id: 1, title: 'Beach House' },
//   { id: 3, title: 'City Apartment' },
//   { id: 5, title: 'Desert Villa' }
// ]

Only listings whose ID is in favoriteIds array!

Render Header

<h1 className="text-3xl font-bold mb-6">
  Your Favorites ({favoriteListings.length})
</h1>

Shows title with count: "Your Favorites (3)"

Handle Empty State

{favoriteListings.length === 0 ? (
  <div className="text-center py-12">
    <p className="text-gray-500 text-lg">
      No favorites yet. Start exploring listings!
    </p>
  </div>
) : (
  <ListingList listings={favoriteListings} />
)}

If no favorites:

  • Show empty state message

If has favorites:

  • Show listings with ListingList component

Understanding the Filter Logic

Let's break down the filter step by step:

const favoriteListings = items.filter((listing) =>
  favoriteIds.includes(listing.id)
);

Alternative: Using a Selector

We could move the filter logic to a selector:

src/state/slices/listingsSlice.js
// Add this selector
export const selectFavoriteListings = (state) => {
  const items = state.listings.items;
  const favoriteIds = state.listings.favorites;
  return items.filter(listing => favoriteIds.includes(listing.id));
};

Then use it in the component:

src/pages/FavoritesPage.jsx
import { useSelector } from 'react-redux';
import { selectFavoriteListings } from '@/state/slices/listingsSlice';
import ListingList from '@/components/ListingList';

function FavoritesPage() {
  const favoriteListings = useSelector(selectFavoriteListings);
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-6">
        Your Favorites ({favoriteListings.length})
      </h1>
      
      {favoriteListings.length === 0 ? (
        <div className="text-center py-12">
          <p className="text-gray-500 text-lg">
            No favorites yet. Start exploring listings!
          </p>
        </div>
      ) : (
        <ListingList listings={favoriteListings} />
      )}
    </div>
  );
}

export default FavoritesPage;

Cleaner! The filter logic is now in the slice where it belongs.

Improving the Empty State

Let's make the empty state more helpful:

src/pages/FavoritesPage.jsx
import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { Heart } from 'lucide-react';
import ListingList from '@/components/ListingList';

function FavoritesPage() {
  const items = useSelector((state) => state.listings.items);
  const favoriteIds = useSelector((state) => state.listings.favorites);
  
  const favoriteListings = items.filter((listing) =>
    favoriteIds.includes(listing.id)
  );
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-6">
        Your Favorites ({favoriteListings.length})
      </h1>
      
      {favoriteListings.length === 0 ? (
        <div className="text-center py-12">
          <Heart size={64} className="mx-auto mb-4 text-gray-300" />
          <h2 className="text-xl font-semibold mb-2 text-gray-700">
            No favorites yet
          </h2>
          <p className="text-gray-500 mb-6">
            Start exploring and save your favorite listings!
          </p>
          <Link 
            to="/" 
            className="inline-block px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
          >
            Browse Listings
          </Link>
        </div>
      ) : (
        <ListingList listings={favoriteListings} />
      )}
    </div>
  );
}

export default FavoritesPage;

Enhanced empty state:

  • ❤️ Heart icon (visual)
  • Clear heading
  • Helpful message
  • Call-to-action button to browse listings

Complete Code Options

src/pages/FavoritesPage.jsx
import { useSelector } from 'react-redux';
import ListingList from '@/components/ListingList';

function FavoritesPage() {
  const items = useSelector((state) => state.listings.items);
  const favoriteIds = useSelector((state) => state.listings.favorites);
  
  const favoriteListings = items.filter((listing) =>
    favoriteIds.includes(listing.id)
  );
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-6">
        Your Favorites ({favoriteListings.length})
      </h1>
      
      {favoriteListings.length === 0 ? (
        <div className="text-center py-12">
          <p className="text-gray-500 text-lg">
            No favorites yet. Start exploring listings!
          </p>
        </div>
      ) : (
        <ListingList listings={favoriteListings} />
      )}
    </div>
  );
}

export default FavoritesPage;

Pros:

  • Simple and direct
  • All logic in one place
  • Easy to understand
src/pages/FavoritesPage.jsx
import { useSelector } from 'react-redux';
import { selectFavoriteListings } from '@/state/slices/listingsSlice';
import ListingList from '@/components/ListingList';

function FavoritesPage() {
  const favoriteListings = useSelector(selectFavoriteListings);
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-6">
        Your Favorites ({favoriteListings.length})
      </h1>
      
      {favoriteListings.length === 0 ? (
        <div className="text-center py-12">
          <p className="text-gray-500 text-lg">
            No favorites yet. Start exploring listings!
          </p>
        </div>
      ) : (
        <ListingList listings={favoriteListings} />
      )}
    </div>
  );
}

export default FavoritesPage;

Pros:

  • Reusable selector
  • Testable logic
  • Follows best practices

Don't forget to add the selector to listingsSlice.js:

src/state/slices/listingsSlice.js
export const selectFavoriteListings = (state) => {
  const items = state.listings.items;
  const favoriteIds = state.listings.favorites;
  return items.filter(listing => favoriteIds.includes(listing.id));
};
src/pages/FavoritesPage.jsx
import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { Heart } from 'lucide-react';
import ListingList from '@/components/ListingList';

function FavoritesPage() {
  const items = useSelector((state) => state.listings.items);
  const favoriteIds = useSelector((state) => state.listings.favorites);
  
  const favoriteListings = items.filter((listing) =>
    favoriteIds.includes(listing.id)
  );
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-6">
        Your Favorites ({favoriteListings.length})
      </h1>
      
      {favoriteListings.length === 0 ? (
        <div className="text-center py-12">
          <Heart size={64} className="mx-auto mb-4 text-gray-300" />
          <h2 className="text-xl font-semibold mb-2 text-gray-700">
            No favorites yet
          </h2>
          <p className="text-gray-500 mb-6">
            Start exploring and save your favorite listings!
          </p>
          <Link 
            to="/" 
            className="inline-block px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
          >
            Browse Listings
          </Link>
        </div>
      ) : (
        <ListingList listings={favoriteListings} />
      )}
    </div>
  );
}

export default FavoritesPage;

Pros:

  • Better UX
  • Clear call-to-action
  • Professional look

What's Next?

Great! The favorites page is ready. In the next lesson, we'll:

  1. Create Navbar - Navigation component with links
  2. Add favorites link - Link to this new page
  3. Show favorite count - Display number of favorites

✅ Lesson Complete! You've created a dedicated page to display favorited listings!

Key Takeaways

  • Filter items by IDs - Combine items and favoriteIds
  • Reuse components - ListingList works for favorites too
  • Handle empty state - Show helpful message when no favorites
  • Use selectors - Move filter logic to slice for reusability
  • Count in header - Display favoriteListings.length
  • Ternary operator - Show empty state OR listings