Like
EngagementAdd like/unlike feature to any content with optimistic updates and real-time counts.
@prisma/client@prisma/adapter-pgpgdotenvzustandlucide-reactprisma@types/pg
9 files generated
Preview
Loading demo...
Install via CLI
npx feature101@latest add likeImport
import { LikeButton } from '@/features/like'Usage
<LikeButton userId="user_123" targetId="post_789" targetType="post" />Database setup required
This feature includes a Prisma model. The CLI runs Skip this and your app will throw:
prisma db push automatically if DATABASE_URL is set at install time. If not, add it to .env then run:npx prisma db pushtable does not exist.Props
| Prop | Type | Default | Description |
|---|---|---|---|
userId* | string | — | User ID performing the like action. |
targetId* | string | — | Unique ID of the content being liked. |
targetType* | "post" | "comment" | "video" | "photo" | "product" | "review" | — | Type of content being liked. |
initialLiked | boolean | false | Start as already liked. |
initialCount | number | 0 | Starting like count. |
showCount | boolean | true | Display like count next to the button. |
className | string | — | Custom CSS classes for styling. |
File layers:clientui componentzustandhook / storeserverserver actionprismaprisma model
Files
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}`); // User can add their own logic
},
onError: (error) => {
console.error("Like error:", error); // User handles errors
},
});
// Fetch real data from DB on mount
useEffect(() => {
refresh();
}, [refresh]);
return (
<button
onClick={toggleLike}
disabled={isLoading}
className={`flex items-center gap-2 transition-all 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 tabular-nums ${
isLiked ? "text-red-500" : "text-gray-600"
}`}
>
{likeCount}
</span>
)}
</button>
);
},
);
LikeButton.displayName = "LikeButton";
export { LikeButton };