M6: State ManagementZustand Path
L7: Create Favorites Page
Build a dedicated page to display all favorited listings
Let's build a page to show all favorited listings! ⭐
What We're Building
A FavoritesPage that:
- Shows all favorited listings
- Displays empty state when no favorites
- Reuses
PropertyCardcomponent - Updates in real-time when favorites change
Step 1: Create the Page Component
import useListingsStore from '@/state/useListingsStore';
import PropertyCard from '@/components/PropertyCard';
function FavoritesPage() {
// Select items and favorites
const items = useListingsStore((state) => state.items);
const favorites = useListingsStore((state) => state.favorites);
// Get favorited listings
const favoritedListings = items.filter((item) =>
favorites.includes(item.id)
);
return (
<div className="favorites-page">
<header className="page-header">
<h1>My Favorites</h1>
<p>
{favoritedListings.length === 0
? 'You haven\'t added any favorites yet'
: `${favoritedListings.length} ${favoritedListings.length === 1 ? 'place' : 'places'} saved`
}
</p>
</header>
{favoritedListings.length === 0 ? (
<div className="empty-state">
<div className="empty-icon">❤️</div>
<h2>No Favorites Yet</h2>
<p>
Start exploring and click the ❤️ button on listings you love!
</p>
<a href="/" className="button-primary">
Explore Listings
</a>
</div>
) : (
<div className="listings-grid">
{favoritedListings.map((listing) => (
<PropertyCard key={listing.id} listing={listing} />
))}
</div>
)}
</div>
);
}
export default FavoritesPage;Understanding the Component
Better: Use Computed Selector
Instead of filtering in the component, let's use the store's getFavoritedItems:
import useListingsStore from '@/state/useListingsStore';
import PropertyCard from '@/components/PropertyCard';
function FavoritesPage() {
// Use computed selector from store
const getFavoritedItems = useListingsStore((state) => state.getFavoritedItems);
const favoritedListings = getFavoritedItems();
return (
<div className="favorites-page">
<header className="page-header">
<h1>My Favorites</h1>
<p>
{favoritedListings.length === 0
? 'You haven\'t added any favorites yet'
: `${favoritedListings.length} ${favoritedListings.length === 1 ? 'place' : 'places'} saved`
}
</p>
</header>
{favoritedListings.length === 0 ? (
<div className="empty-state">
<div className="empty-icon">❤️</div>
<h2>No Favorites Yet</h2>
<p>
Start exploring and click the ❤️ button on listings you love!
</p>
<a href="/" className="button-primary">
Explore Listings
</a>
</div>
) : (
<div className="listings-grid">
{favoritedListings.map((listing) => (
<PropertyCard key={listing.id} listing={listing} />
))}
</div>
)}
</div>
);
}
export default FavoritesPage;Comparison: Component Filter vs Store Selector
Filter in component:
function FavoritesPage() {
const items = useListingsStore((state) => state.items);
const favorites = useListingsStore((state) => state.favorites);
// Filter here
const favoritedListings = items.filter((item) =>
favorites.includes(item.id)
);
return <div>{/* use favoritedListings */}</div>;
}Pros:
- Simple and direct
- All logic visible in component
Cons:
- Repeated if used in multiple components
- Re-calculates on every render
- Component has more responsibilities
Filter in store:
// In store
const useListingsStore = create((set, get) => ({
items: [],
favorites: [],
getFavoritedItems: () => {
const { items, favorites } = get();
return items.filter((item) => favorites.includes(item.id));
},
}));// In component
function FavoritesPage() {
const getFavoritedItems = useListingsStore((state) => state.getFavoritedItems);
const favoritedListings = getFavoritedItems();
return <div>{/* use favoritedListings */}</div>;
}Pros:
- Reusable across components
- Logic centralized in store
- Component stays simple
Cons:
- Still re-calculates on every render
- Need to remember to use the selector
Use component filtering when:
- Only needed in one place
- Simple, one-off logic
- Component-specific requirements
// Example: Filter by search (component-specific)
const filtered = items.filter(item =>
item.name.includes(searchQuery)
);Use store selector when:
- Needed in multiple components
- Core business logic
- Complex calculations
// Example: Get favorited items (used everywhere)
const getFavoritedItems = useListingsStore((state) => state.getFavoritedItems);Best practice: Start with component filtering, move to store when you need it elsewhere!
Step 2: Add Styling
.favorites-page {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.page-header {
text-align: center;
margin-bottom: 3rem;
}
.page-header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
}
.page-header p {
font-size: 1.2rem;
color: #666;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 4rem 2rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.empty-icon {
font-size: 5rem;
margin-bottom: 1rem;
opacity: 0.3;
}
.empty-state h2 {
font-size: 1.75rem;
margin-bottom: 0.5rem;
color: #333;
}
.empty-state p {
font-size: 1.1rem;
color: #666;
margin-bottom: 2rem;
}
.button-primary {
display: inline-block;
padding: 0.75rem 2rem;
background: #0369a1;
color: white;
text-decoration: none;
border-radius: 4px;
font-weight: 600;
transition: background 0.2s;
}
.button-primary:hover {
background: #075985;
}Step 3: Add to Router
Update your router to include the FavoritesPage:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import HomePage from '@/pages/HomePage';
import FavoritesPage from '@/pages/FavoritesPage';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/favorites" element={<FavoritesPage />} />
</Routes>
</BrowserRouter>
);
}
export default App;Test the Flow
Try this sequence:
- Go to
HomePage(/) - Click favorite button on 3 listings → ❤️ ❤️ ❤️
- Navigate to
/favorites - See your 3 favorited listings! ✅
- Click favorite button on one listing → 🤍
- It disappears from favorites page! ✅
- Go back to HomePage
- That listing shows 🤍 (not favorited)
Everything stays in sync! 🎉
Real-Time Updates
What's Next?
Perfect! FavoritesPage is complete. In the next lesson:
- Create Navbar - Navigation with favorites count
- Add logo - Branding
- Active link highlighting - Show current page
- Responsive design - Mobile-friendly
✅ Lesson Complete! FavoritesPage displays favorited listings with empty state handling!
Key Takeaways
- ✅ Reused PropertyCard - Same component in HomePage and FavoritesPage
- ✅ Empty state - Clear feedback when no favorites
- ✅ Real-time updates - Removing favorite updates UI immediately
- ✅ Shared data - No duplicate fetching or state
- ✅ Computed selector - getFavoritedItems() centralizes logic
- ✅ Loading states - Handles fetch lifecycle properly
- ✅ Consistent UX - Same patterns as HomePage
- ✅ Simpler than Redux - Less boilerplate, same functionality