From 45e45328592777f1de3a2633e72ec21c1797e1ae Mon Sep 17 00:00:00 2001 From: "P. Clawmogorov" <262173731+Alm0stSurely@users.noreply.github.com> Date: Sun, 12 Apr 2026 10:09:53 +0000 Subject: [PATCH] fix(watch-button): eliminate N+1 API calls on profile watchlist ## Problem The WatchButton component was firing N separate checkIsWatched() API calls for N repos in the watchlist, even though the parent already knew these repos were watched by definition. ## Solution - Add optional initialIsWatched prop to skip redundant API calls - Add optional onUnwatch callback for optimistic UI removal - Profile page now passes initialIsWatched={true} for all watchlist items ## Result Profile page with 10 watched repos now fires 1 API call instead of 11. Fixes #57 --- frontend/app/(auth)/profile/page.tsx | 7 ++++++- frontend/components/watch-button.tsx | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/frontend/app/(auth)/profile/page.tsx b/frontend/app/(auth)/profile/page.tsx index 9e4f42d..1dbae77 100644 --- a/frontend/app/(auth)/profile/page.tsx +++ b/frontend/app/(auth)/profile/page.tsx @@ -274,7 +274,12 @@ function Watchlist() {
- + setRepos(prev => prev.filter(r => r.id !== repo.id))} + />
diff --git a/frontend/components/watch-button.tsx b/frontend/components/watch-button.tsx index a804417..f6a38fc 100644 --- a/frontend/components/watch-button.tsx +++ b/frontend/components/watch-button.tsx @@ -9,21 +9,28 @@ import { toast } from "sonner"; interface WatchButtonProps { owner: string; name: string; + initialIsWatched?: boolean; + onUnwatch?: () => void; } -export function WatchButton({ owner, name }: WatchButtonProps) { +export function WatchButton({ owner, name, initialIsWatched, onUnwatch }: WatchButtonProps) { const { token } = useAuth(); - const [isWatched, setIsWatched] = useState(false); - const [loading, setLoading] = useState(true); + const [isWatched, setIsWatched] = useState(initialIsWatched ?? false); + const [loading, setLoading] = useState(initialIsWatched === undefined); useEffect(() => { + // Skip the API call if the parent gave us the initial state + if (initialIsWatched !== undefined) { + setLoading(false); + return; + } if (token) { checkIsWatched(token, owner, name) .then(setIsWatched) .catch(() => setIsWatched(false)) .finally(() => setLoading(false)); } - }, [token, owner, name]); + }, [token, owner, name, initialIsWatched]); const toggleWatch = async () => { if (!token) return; @@ -34,6 +41,7 @@ export function WatchButton({ owner, name }: WatchButtonProps) { await removeFromWatchlist(token, owner, name); setIsWatched(false); toast.success("Removed from watchlist"); + onUnwatch?.(); } else { await addToWatchlist(token, owner, name); setIsWatched(true);