Like

Engagement

Add like/unlike feature to any content with optimistic updates and real-time counts.

Demo

Loading demo...

Install via CLI

npx feature101@latest add like
components/LikeButton.tsx
"use client";

import { memo, useEffect } from "react";

import { useLike } from "../like.hooks";
import { Heart } from "lucide-react";

interface LikeButtonProps {
  targetId: string; // "post_123" - what we're liking
  targetType: "post" | "comment" | "video" | "photo" | "product" | "review"; // Type of thing
  userId: string; // "user_xyz" - who's liking
  initialLiked?: boolean; // Start as liked? (optional, default: false)
  initialCount?: number; // Start count (optional, default: 0)
  showCount?: boolean; // Show the number? (optional, default: true)
  className?: string; // Custom CSS classes (optional)
}

const LikeButton = memo(
  ({
    targetId,
    targetType,
    userId,
    initialLiked = false,
    initialCount = 0,
    showCount = true,
    className = "",
  }: LikeButtonProps) => {
    const { isLiked, likeCount, isLoading, toggleLike, refresh } = useLike({
      targetId,
      targetType,
      userId,
      initialLiked,
      initialCount,
      onSuccess: (liked, count) => {
        console.log(`Like ${liked ? "added" : "removed"}. New count: ${count}`);
      },
      onError: (error) => {
        console.error("Like error:", error);
      },
    });

    //  Fetch real data from DB on mount
    useEffect(() => {
      refresh();
    }, [refresh]);

    return (
      <button
        onClick={toggleLike}
        disabled={isLoading}
        className={`flex items-center gap-2 transition-all ${
          isLoading ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
        } ${className}`}
        aria-label={isLiked ? "Unlike" : "Like"}
      >
        <Heart
          className={`w-5 h-5 transition-all ${
            isLiked
              ? "fill-red-500 text-red-500"
              : "text-gray-400 hover:text-red-500"
          }`}
        />
        {showCount && (
          <span
            className={`text-sm font-medium ${
              isLiked ? "text-red-500" : "text-gray-600"
            }`}
          >
            {likeCount}
          </span>
        )}
      </button>
    );
  },
);

LikeButton.displayName = "LikeButton";

export { LikeButton };