lens-blocks
componentfollow-buttonsource: ok · hit
Lens Follow Button
A button component to follow or unfollow a Lens Account, displaying the current follow state.
npx shadcn add "https://lensblocks.com/r/follow-button.json"free
Where it goes
stats
Interaction
animated-counter
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · light@lens-protocol/react@0.0.0-canary-20250820102520@lens-protocol/client@0.0.0-canary-20250820102520viem@2.33.0
Registry dependencies
buttonspinner
Files2 · source cached 10/8/2026, 4:09:06 PM
registry/new-york/components/account/lens-follow-button.tsx
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/registry/new-york/ui/button";
import { Account, SessionClient, TxHash, UnauthenticatedError } from "@lens-protocol/react";
import { follow, unfollow } from "@lens-protocol/client/actions";
import { handleOperationWith } from "@lens-protocol/client/viem";
import { WalletClient } from "viem";
import { Spinner } from "@/registry/new-york/ui/spinner";
import { isResult, Result } from "@/registry/new-york/lib/result";
type Props = {
account: Account | Result<Account>;
session: Result<SessionClient>;
wallet?: { data: WalletClient | undefined | null; isLoading?: boolean; error?: unknown };
onFollowSuccess?: (account: Account, txHash: TxHash) => void;
onUnfollowSuccess?: (account: Account, txHash: TxHash) => void;
onFollowError?: (account: Account, error: Error) => void;
};
export const LensFollowButton = ({ ...props }: Props) => {
const { account: accountRes, session, wallet, onFollowSuccess, onUnfollowSuccess, onFollowError } = props;
const [isSubmitting, setIsSubmitting] = useState(false);
const account = isResult(accountRes) ? accountRes?.data : accountRes;
const sessionClient = session?.data;
const walletClient = wallet?.data;
const accountLoading = accountRes && "loading" in accountRes ? accountRes.loading : false;
const [optimisticIsFollowed, setOptimisticIsFollowed] = useState<boolean>(
account?.operations?.isFollowedByMe ?? false,
);
useEffect(() => {
setOptimisticIsFollowed(account?.operations?.isFollowedByMe ?? false);
}, [account?.operations]);
const handleUnfollow = async () => {
if (!account || !sessionClient) return;
if (!walletClient) {
onFollowError?.(account, new Error("Wallet client is not available"));
return;
}
const res = await unfollow(sessionClient, {
account: account.address,
})
.andThen(handleOperationWith(walletClient))
.andThen(sessionClient.waitForTransaction);
if (res.isErr()) {
onFollowError?.(account, res.error);
return;
}
setOptimisticIsFollowed(false);
onUnfollowSuccess?.(account, res.value);
};
const handleFollow = async () => {
if (!account || !sessionClient) return;
if (!walletClient) {
onFollowError?.(account, new Error("Wallet client is not available"));
return;
}
const res = await follow(sessionClient, {
account: account.address,
})
.andThen(handleOperationWith(walletClient))
.andThen(sessionClient.waitForTransaction);
if (res.isErr()) {
onFollowError?.(account, res.error);
return;
}
setOptimisticIsFollowed(true);
onFollowSuccess?.(account, res.value);
};
const onFollowButtonClick = async () => {
if (!account) return;
if (!session) {
onFollowError?.(account, new UnauthenticatedError());
return;
}
setIsSubmitting(true);
try {
if (optimisticIsFollowed) {
await handleUnfollow();
} else {
await handleFollow();
}
} catch (e) {
if (e instanceof Error) {
onFollowError?.(account, e);
}
} finally {
setIsSubmitting(false);
}
};
return (
<Button
variant={optimisticIsFollowed ? "outline" : "default"}
className="group hover:border-destructive"
onClick={onFollowButtonClick}
disabled={isSubmitting || accountLoading || session?.loading || wallet?.isLoading}
>
{isSubmitting ? (
<>
<Spinner /> {optimisticIsFollowed ? "Unfollowing..." : "Following..."}
</>
) : (
<>
<span className="hidden group-hover:inline">{optimisticIsFollowed ? "Unfollow" : "Follow"}</span>
<span className="inline group-hover:hidden">{optimisticIsFollowed ? "Following" : "Follow"}</span>
</>
)}
</Button>
);
};
registry/new-york/lib/result.ts
export type Result<T, E = never> = {
data: T | null | undefined;
error: E | null | undefined;
loading: boolean;
};
export const isResult = <T, E = never>(result: any): result is Result<T, E> => {
return "data" in result && "error" in result && "loading" in result;
};
export const isLoading = (result: any): boolean => {
if (isResult(result)) {
return result.loading;
}
return false;
};