Code To Learn logo

Code To Learn

M6: State ManagementRedux Toolkit Path

L14: Add to PropertyCard

Integrate the favorite button into PropertyCard and ListingDetailsCard

Let's add the favorite button to our listing cards so users can favorite listings!

Current PropertyCard Structure

Our PropertyCard component displays listing information:

src/components/PropertyCard.jsx
function PropertyCard({ listing }) {
  return (
    <div className="border rounded-lg overflow-hidden hover:shadow-lg transition-shadow">
      <img 
        src={listing.images[0]} 
        alt={listing.title}
        className="w-full h-48 object-cover"
      />
      <div className="p-4">
        <h3 className="font-semibold text-lg mb-2">{listing.title}</h3>
        <p className="text-gray-600 mb-2">{listing.description}</p>
        <p className="text-lg font-bold">${listing.price}/night</p>
      </div>
    </div>
  );
}

We need to add the favorite button in the top-right corner of the image.

Step 1: Import the Button

Add the import at the top:

src/components/PropertyCard.jsx
import ListingFavoriteButton from './ListingFavoriteButton';

function PropertyCard({ listing }) {
  return (
    <div className="border rounded-lg overflow-hidden hover:shadow-lg transition-shadow">
      <img 
        src={listing.images[0]} 
        alt={listing.title}
        className="w-full h-48 object-cover"
      />
      <div className="p-4">
        <h3 className="font-semibold text-lg mb-2">{listing.title}</h3>
        <p className="text-gray-600 mb-2">{listing.description}</p>
        <p className="text-lg font-bold">${listing.price}/night</p>
      </div>
    </div>
  );
}

Step 2: Add Relative Positioning

Wrap the image in a container with relative positioning:

src/components/PropertyCard.jsx
import ListingFavoriteButton from './ListingFavoriteButton';

function PropertyCard({ listing }) {
  return (
    <div className="border rounded-lg overflow-hidden hover:shadow-lg transition-shadow">
      <div className="relative">
        <img 
          src={listing.images[0]} 
          alt={listing.title}
          className="w-full h-48 object-cover"
        />
      </div>
      <div className="p-4">
        <h3 className="font-semibold text-lg mb-2">{listing.title}</h3>
        <p className="text-gray-600 mb-2">{listing.description}</p>
        <p className="text-lg font-bold">${listing.price}/night</p>
      </div>
    </div>
  );
}

Why relative? So the button can be positioned absolutely inside it!

Step 3: Add the Button

Add the button inside the relative container:

src/components/PropertyCard.jsx
import ListingFavoriteButton from './ListingFavoriteButton';

function PropertyCard({ listing }) {
  return (
    <div className="border rounded-lg overflow-hidden hover:shadow-lg transition-shadow">
      <div className="relative">
        <img 
          src={listing.images[0]} 
          alt={listing.title}
          className="w-full h-48 object-cover"
        />
        <div className="absolute top-2 right-2">
          <ListingFavoriteButton listingId={listing.id} />
        </div>
      </div>
      <div className="p-4">
        <h3 className="font-semibold text-lg mb-2">{listing.title}</h3>
        <p className="text-gray-600 mb-2">{listing.description}</p>
        <p className="text-lg font-bold">${listing.price}/night</p>
      </div>
    </div>
  );
}

That's it! The button now appears in the top-right corner! 🎉

Complete PropertyCard Code

Here's the complete updated component:

src/components/PropertyCard.jsx
import { Link } from 'react-router-dom';
import ListingFavoriteButton from './ListingFavoriteButton';

function PropertyCard({ listing }) {
  return (
    <Link 
      to={`/listings/${listing.id}`}
      className="block border rounded-lg overflow-hidden hover:shadow-lg transition-shadow"
    >
      <div className="relative">
        <img 
          src={listing.images[0]} 
          alt={listing.title}
          className="w-full h-48 object-cover"
        />
        <div className="absolute top-2 right-2">
          <ListingFavoriteButton listingId={listing.id} />
        </div>
      </div>
      <div className="p-4">
        <h3 className="font-semibold text-lg mb-2">{listing.title}</h3>
        <p className="text-gray-600 text-sm mb-2 line-clamp-2">
          {listing.description}
        </p>
        <p className="text-lg font-bold">${listing.price}/night</p>
      </div>
    </Link>
  );
}

export default PropertyCard;

Understanding the Positioning

Let's break down how the positioning works:

Add to ListingDetailsCard

Let's also add the button to the details page:

src/components/ListingDetailsCard.jsx
import ListingFavoriteButton from './ListingFavoriteButton';

function ListingDetailsCard({ listing }) {
  return (
    <div className="max-w-4xl mx-auto p-6">
      <div className="relative mb-6">
        <img 
          src={listing.images[0]} 
          alt={listing.title}
          className="w-full h-96 object-cover rounded-lg"
        />
        <div className="absolute top-4 right-4">
          <ListingFavoriteButton listingId={listing.id} />
        </div>
      </div>
      
      <div className="mb-6">
        <h1 className="text-3xl font-bold mb-2">{listing.title}</h1>
        <p className="text-gray-600 mb-4">{listing.description}</p>
        <p className="text-2xl font-bold">${listing.price}/night</p>
      </div>
      
      {/* More details... */}
    </div>
  );
}

export default ListingDetailsCard;

Same pattern:

  1. Wrap image in relative container
  2. Add button with absolute positioning
  3. Position in top-right corner

Styling Variations

Standard placement (what we have):

<div className="absolute top-2 right-2">
  <ListingFavoriteButton listingId={listing.id} />
</div>

Clean and simple!

Add background for better visibility:

<div className="absolute top-2 right-2 bg-white/80 backdrop-blur-sm rounded-full">
  <ListingFavoriteButton listingId={listing.id} />
</div>

Classes explained:

  • bg-white/80 - 80% opaque white background
  • backdrop-blur-sm - Blur content behind
  • rounded-full - Circular background

Better for images with light colors!

Alternative position:

<div className="absolute bottom-2 right-2">
  <ListingFavoriteButton listingId={listing.id} />
</div>

Positioned at bottom instead of top!

Add shadow for depth:

<div className="absolute top-2 right-2 shadow-lg rounded-full">
  <ListingFavoriteButton listingId={listing.id} />
</div>

Shadow makes button stand out more!

Enhanced Version with Background

For better visibility on all images:

src/components/PropertyCard.jsx
import { Link } from 'react-router-dom';
import ListingFavoriteButton from './ListingFavoriteButton';

function PropertyCard({ listing }) {
  return (
    <Link 
      to={`/listings/${listing.id}`}
      className="block border rounded-lg overflow-hidden hover:shadow-lg transition-shadow"
    >
      <div className="relative">
        <img 
          src={listing.images[0]} 
          alt={listing.title}
          className="w-full h-48 object-cover"
        />
        <div className="absolute top-2 right-2 bg-white/90 backdrop-blur-sm rounded-full shadow-md">
          <ListingFavoriteButton listingId={listing.id} />
        </div>
      </div>
      <div className="p-4">
        <h3 className="font-semibold text-lg mb-2">{listing.title}</h3>
        <p className="text-gray-600 text-sm mb-2 line-clamp-2">
          {listing.description}
        </p>
        <p className="text-lg font-bold">${listing.price}/night</p>
      </div>
    </Link>
  );
}

export default PropertyCard;

Added:

  • bg-white/90 - Semi-transparent white background
  • backdrop-blur-sm - Blur effect
  • shadow-md - Medium shadow
  • rounded-full - Circular container

Result: Button visible on any image color! ✨

Testing the Integration

Let's verify everything works:

Check HomePage

Go to the homepage - each listing card should have a heart button!

Expected:

  • Button in top-right of each image
  • Gray outline hearts (not favorited yet)

Click a Button

Click a heart button.

Expected:

  • Heart turns red and fills
  • Navbar counter increases by 1
  • Redux DevTools shows ID added to favorites

Click Again

Click the same heart button.

Expected:

  • Heart turns gray and unfills
  • Navbar counter decreases by 1
  • Redux DevTools shows ID removed from favorites

Perfect toggle behavior! 🎉

Check Favorites Page

Favorite a few listings, then go to Favorites page.

Expected:

  • Favorited listings shown
  • Hearts filled (red)
  • Can unfavorite from this page too

Check Details Page

Click a listing to go to details.

Expected:

  • Heart button on details page
  • Synced with card state (if favorited on card, also favorited on details)

State Synchronization

The beauty of Redux: all buttons stay in sync!

Common Issues & Solutions

What's Next?

Excellent! The favorites feature is complete. In the final lesson, we'll:

  1. Review all concepts - Redux patterns we learned
  2. Complete code walkthrough - End-to-end favorites feature
  3. Best practices - When to use Redux vs local state
  4. Performance tips - Optimizing Redux apps

✅ Lesson Complete! The favorite button is now integrated into all listing cards!

Key Takeaways

  • Relative + absolute positioning for overlay elements
  • Wrap image in div to create positioning context
  • top-2 right-2 for corner positioning
  • stopPropagation() prevents click bubbling to parent Link
  • Redux synchronization - all buttons stay in sync automatically
  • Reusable component - works in cards, details, anywhere
  • Background/shadow improves visibility on any image