Like
EngagementAdd like/unlike feature to any content with optimistic updates and real-time counts.
Demo
Loading demo...
Install via CLI
npx feature101@latest add likeFiles
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 };