L8: Favorites Setup
Test the toggleFavorite reducer and prepare for favorites UI
We already have a toggleFavorite reducer in our slice! Let's test it and prepare to use it in our UI.
Review: toggleFavorite Reducer
Back in Lesson 3, we created this reducer:
reducers: {
toggleFavorite: (state, action) => {
const id = action.payload;
if (state.favorites.includes(id)) {
// Remove from favorites
state.favorites = state.favorites.filter(favoriteId => favoriteId !== id);
} else {
// Add to favorites
state.favorites.push(id);
}
},
}What it does:
- Takes a listing ID as payload
- If ID is in favorites → remove it
- If ID not in favorites → add it
- Perfect for toggle buttons!
Understanding the Favorites State
The favorites array stores listing IDs:
// Initial state
{
favorites: []
}
// After favoriting listings 1, 5, and 9
{
favorites: [1, 5, 9]
}
// After unfavoriting listing 5
{
favorites: [1, 9]
}Why store IDs instead of full listing objects?
How to Get Favorited Listings
Even though we store IDs, we can easily get the full listing data:
function FavoritesPage() {
const items = useSelector((state) => state.listings.items);
const favoriteIds = useSelector((state) => state.listings.favorites);
// Get full listing objects for favorited IDs
const favoriteListings = items.filter(listing =>
favoriteIds.includes(listing.id)
);
return (
<div>
{favoriteListings.map(listing => (
<PropertyCard key={listing.id} listing={listing} />
))}
</div>
);
}Pattern:
- Get all listings from
state.listings.items - Get favorite IDs from
state.listings.favorites - Filter listings to only those with IDs in favorites array
Simple and efficient! ✨
Testing toggleFavorite
Let's test the reducer works correctly. Open Redux DevTools:
Open Redux DevTools
- Open your app in browser
- Open DevTools (F12)
- Click Redux tab
Check Initial State
In the State tab, you should see:
{
"listings": {
"items": [/* listings */],
"favorites": [],
"status": "succeeded",
"error": null
}
}Favorites is empty initially.
Manually Dispatch Toggle
In the Dispatch section, manually dispatch:
{
"type": "listings/toggleFavorite",
"payload": 1
}Click Dispatch.
Verify State Updated
Check the State tab again:
{
"listings": {
"items": [/* listings */],
"favorites": [1],
"status": "succeeded",
"error": null
}
}ID 1 was added! 🎉
Toggle Again (Remove)
Dispatch the same action again:
{
"type": "listings/toggleFavorite",
"payload": 1
}Check state - 1 should be removed:
{
"listings": {
"favorites": []
}
}Perfect! The toggle works both ways! ✨
Add Multiple Favorites
Try adding multiple:
{ "type": "listings/toggleFavorite", "payload": 1 }
{ "type": "listings/toggleFavorite", "payload": 5 }
{ "type": "listings/toggleFavorite", "payload": 9 }State should show:
{
"listings": {
"favorites": [1, 5, 9]
}
}Excellent! 🎉
Selector Patterns for Favorites
Here are useful selectors you'll use:
Get the array of favorite IDs:
import { useSelector } from 'react-redux';
function Component() {
const favoriteIds = useSelector((state) => state.listings.favorites);
console.log(favoriteIds); // [1, 5, 9]
}Use when: You just need the IDs (for checking, counting, etc.)
Get the full listing objects:
import { useSelector } from 'react-redux';
function Component() {
const items = useSelector((state) => state.listings.items);
const favoriteIds = useSelector((state) => state.listings.favorites);
const favoriteListings = items.filter(listing =>
favoriteIds.includes(listing.id)
);
console.log(favoriteListings);
// [
// { id: 1, title: 'Beach House', ... },
// { id: 5, title: 'Mountain Cabin', ... },
// { id: 9, title: 'City Apartment', ... }
// ]
}Use when: You need to display favorited listings
Check if a specific listing is favorited:
import { useSelector } from 'react-redux';
function PropertyCard({ listing }) {
const favoriteIds = useSelector((state) => state.listings.favorites);
const isFavorited = favoriteIds.includes(listing.id);
return (
<div>
<h3>{listing.title}</h3>
<button>
{isFavorited ? '❤️ Favorited' : '🤍 Favorite'}
</button>
</div>
);
}Use when: You need to show if a listing is favorited (for button state)
Get the number of favorites:
import { useSelector } from 'react-redux';
function Navbar() {
const favoriteCount = useSelector((state) => state.listings.favorites.length);
return (
<nav>
<a href="/favorites">
Favorites ({favoriteCount})
</a>
</nav>
);
}Use when: You need to display a count (badge, counter, etc.)
Creating Selector Functions (Best Practice)
Instead of writing selectors inline, create reusable selector functions:
// At the bottom of the file, after the slice
// Selectors
export const selectAllListings = (state) => state.listings.items;
export const selectFavoriteIds = (state) => state.listings.favorites;
export const selectListingsStatus = (state) => state.listings.status;
export const selectListingsError = (state) => state.listings.error;
// Computed selector - favorited listings
export const selectFavoriteListings = (state) => {
const items = state.listings.items;
const favoriteIds = state.listings.favorites;
return items.filter(listing => favoriteIds.includes(listing.id));
};
// Computed selector - is listing favorited?
export const selectIsListingFavorited = (state, listingId) => {
return state.listings.favorites.includes(listingId);
};
// Computed selector - favorite count
export const selectFavoriteCount = (state) => state.listings.favorites.length;Usage in components:
import { useSelector } from 'react-redux';
import {
selectFavoriteListings,
selectFavoriteCount
} from '@/state/slices/listingsSlice';
function FavoritesPage() {
const favoriteListings = useSelector(selectFavoriteListings);
const count = useSelector(selectFavoriteCount);
return (
<div>
<h1>Your Favorites ({count})</h1>
{favoriteListings.map(listing => (
<PropertyCard key={listing.id} listing={listing} />
))}
</div>
);
}Benefits:
- ✅ Reusable across components
- ✅ Easy to test
- ✅ Single source of truth
- ✅ Can be memoized for performance
- ✅ Self-documenting
Dispatching toggleFavorite
To use toggleFavorite in components:
import { useDispatch } from 'react-redux';
import { toggleFavorite } from '@/state/slices/listingsSlice';
function PropertyCard({ listing }) {
const dispatch = useDispatch();
const handleToggleFavorite = () => {
dispatch(toggleFavorite(listing.id));
};
return (
<div>
<h3>{listing.title}</h3>
<button onClick={handleToggleFavorite}>
Toggle Favorite
</button>
</div>
);
}Flow:
- User clicks button
handleToggleFavoriteruns- Dispatches
toggleFavorite(listing.id) - Reducer adds/removes ID from favorites
- Components re-render with new state
Understanding the Toggle Pattern
The toggle pattern is common in Redux:
Our current pattern - add or remove:
toggleFavorite: (state, action) => {
const id = action.payload;
if (state.favorites.includes(id)) {
state.favorites = state.favorites.filter(fav => fav !== id);
} else {
state.favorites.push(id);
}
}Use when: Managing arrays of IDs (favorites, selected items, tags)
Toggle boolean values:
toggleSidebar: (state) => {
state.sidebarOpen = !state.sidebarOpen;
}
toggleDarkMode: (state) => {
state.darkMode = !state.darkMode;
}Use when: Simple on/off states (modals, sidebars, themes)
Cycle through multiple states:
cycleView: (state) => {
const views = ['grid', 'list', 'map'];
const currentIndex = views.indexOf(state.view);
const nextIndex = (currentIndex + 1) % views.length;
state.view = views[nextIndex];
}Use when: Cycling through options (views, themes, sort orders)
Preparing for UI
In the next lessons, we'll build:
Favorites Page (Lesson 9)
Display all favorited listings:
function FavoritesPage() {
const favoriteListings = useSelector(selectFavoriteListings);
return (
<div>
<h1>Your Favorites</h1>
{favoriteListings.map(listing => (
<PropertyCard key={listing.id} listing={listing} />
))}
</div>
);
}Navbar with Link (Lesson 10-11)
Navigation with favorites count:
function Navbar() {
const count = useSelector(selectFavoriteCount);
return (
<nav>
<Link to="/favorites">
❤️ Favorites ({count})
</Link>
</nav>
);
}Favorite Button (Lesson 13-14)
Toggle button on listing cards:
function ListingFavoriteButton({ listingId }) {
const dispatch = useDispatch();
const isFavorited = useSelector((state) =>
selectIsListingFavorited(state, listingId)
);
return (
<button onClick={() => dispatch(toggleFavorite(listingId))}>
{isFavorited ? '❤️' : '🤍'}
</button>
);
}What's Next?
Perfect! The favorites system is ready to use. In the next lesson, we'll:
- Create FavoritesPage - New page to display favorites
- Use selectors - Get favorited listings from Redux
- Handle empty state - Show message when no favorites
✅ Lesson Complete! You've tested the toggleFavorite reducer and learned how to work with favorites in Redux!
Key Takeaways
- ✅ Store IDs, not objects - More efficient and easier to manage
- ✅ Filter items to get full listing objects from IDs
- ✅ Toggle pattern - Add if not present, remove if present
- ✅ Create selectors for reusable state access
- ✅ Computed selectors derive data from existing state
- ✅ Test reducers with Redux DevTools manual dispatch
- ✅
.includes()checks if ID is in favorites array