Follow
EngagementAdd follow/unfollow functionality to user profiles.
@prisma/client@prisma/adapter-pgpgdotenvzustandprisma@types/pg
shadcn/uibutton— auto-installed by CLI
9 files generated
Preview
23 followers
Install via CLI
npx feature101@latest add followImport
import { FollowButton } from '@/features/follow'Usage
<FollowButton currentUserId="user_123" targetUserId="user_xyz" />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 |
|---|---|---|---|
currentUserId* | string | — | ID of the user clicking follow. |
targetUserId* | string | — | ID of the user being followed. |
initialIsFollowing | boolean | false | Start as already following. |
initialFollowerCount | number | 0 | Starting follower count. |
showCount | boolean | true | Display follower count next to the button. |
className | string | — | Custom CSS classes for styling. |
onFollowChange | (isFollowing: boolean) => void | — | Callback fired when follow state changes. |
File layers:clientui componentzustandhook / storeserverserver actionprismaprisma model
Files
components/FollowButton.tsx
"use client";
import { memo } from "react";
import { Button } from "@/components/ui/button";
import { useFollow } from "../follow.hooks";
import type { FollowButtonProps } from "../follow.types";
const FollowButton = memo(
({
currentUserId,
targetUserId,
initialIsFollowing,
initialFollowerCount,
showCount = true,
className = "",
onFollowChange,
}: FollowButtonProps) => {
const { isFollowing, followerCount, isLoading, error, toggleFollow } =
useFollow({
currentUserId,
targetUserId,
initialIsFollowing,
initialFollowerCount,
});
const handleClick = async () => {
await toggleFollow();
onFollowChange?.(!isFollowing);
};
if (currentUserId === targetUserId) return null;
return (
<div className={`inline-flex items-center gap-2 ${className}`}>
<Button
onClick={handleClick}
disabled={isLoading}
variant={isFollowing ? "outline" : "default"}
aria-label={isFollowing ? "Unfollow" : "Follow"}
>
{isFollowing ? "Following" : "Follow"}
</Button>
{showCount && (
<span className="text-sm text-muted-foreground">
{followerCount} {followerCount === 1 ? "follower" : "followers"}
</span>
)}
{error && (
<span className="text-xs text-destructive">{error}</span>
)}
</div>
);
},
);
FollowButton.displayName = "FollowButton";
export default FollowButton;