Follow

Engagement

Add 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 follow

Import

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 prisma db push automatically if DATABASE_URL is set at install time. If not, add it to .env then run:
npx prisma db push
Skip this and your app will throw: table does not exist.

Props

PropTypeDescription
currentUserId*
stringID of the user clicking follow.
targetUserId*
stringID of the user being followed.
initialIsFollowing
booleanStart as already following.
initialFollowerCount
numberStarting follower count.
showCount
booleanDisplay follower count next to the button.
className
stringCustom CSS classes for styling.
onFollowChange
(isFollowing: boolean) => voidCallback fired when follow state changes.
File layers:clientui componentzustandhook / storeserverserver actionprismaprisma model
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;