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:
- Shows all favorited listings
- Handles empty state (no favorites)
- Displays count of favorites
- Uses existing
ListingListcomponent
Step 1: Create the Page File
Create a new file:
touch src/pages/FavoritesPage.jsxStep 2: Build the Component
Add the basic structure:
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 storeListingList- 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
ListingListcomponent
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:
// 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:
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:
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
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
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:
export const selectFavoriteListings = (state) => {
const items = state.listings.items;
const favoriteIds = state.listings.favorites;
return items.filter(listing => favoriteIds.includes(listing.id));
};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:
- Create Navbar - Navigation component with links
- Add favorites link - Link to this new page
- 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
itemsandfavoriteIds - ✅ Reuse components -
ListingListworks 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