Skip to content

Commit d959ae7

Browse files
committed
fix(ui): fix favorite button not working on skill detail page
- Change fetcher.submit({}) to fetcher.submit(null) for proper empty POST - Add type="button" to prevent implicit form submission behavior - Add error handling to revert optimistic UI on server failure - Disable button during all non-idle fetcher states to prevent double-clicks - Make recomputeSkillScores fire-and-forget to avoid blocking response
1 parent 247aee7 commit d959ae7

2 files changed

Lines changed: 19 additions & 12 deletions

File tree

apps/web/app/components/favorite-button.tsx

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Heart } from "lucide-react";
2+
import { useEffect } from "react";
23
import { useFetcher } from "react-router";
34

45
interface FavoriteButtonProps {
@@ -12,24 +13,28 @@ export function FavoriteButton({
1213
isFavorited,
1314
onToggle,
1415
}: FavoriteButtonProps) {
15-
const fetcher = useFetcher();
16+
const fetcher = useFetcher<{ favorited?: boolean; error?: string }>();
1617

17-
const handleClick = () => {
18-
fetcher.submit(
19-
{},
20-
{
21-
method: "post",
22-
action: `/api/skills/${skillSlug}/favorite`,
23-
}
24-
);
18+
// Revert optimistic UI if server returned an error
19+
useEffect(() => {
20+
if (fetcher.data?.error) {
21+
onToggle?.(!isFavorited);
22+
}
23+
}, [fetcher.data]);
2524

25+
const handleClick = () => {
26+
fetcher.submit(null, {
27+
method: "post",
28+
action: `/api/skills/${skillSlug}/favorite`,
29+
});
2630
onToggle?.(!isFavorited);
2731
};
2832

29-
const isLoading = fetcher.state === "submitting";
33+
const isLoading = fetcher.state !== "idle";
3034

3135
return (
3236
<button
37+
type="button"
3338
onClick={handleClick}
3439
disabled={isLoading}
3540
className="flex items-center gap-2 rounded-lg border border-sx-border bg-sx-bg px-4 py-2 text-sm font-medium transition-colors hover:bg-sx-bg-hover disabled:opacity-50"

apps/web/app/routes/api.skill-favorite.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ export async function action({ request, params, context }: ActionFunctionArgs) {
6767
favorited = true;
6868
}
6969

70-
// Recompute leaderboard scores
71-
await recomputeSkillScores(db, skill.id);
70+
// Recompute leaderboard scores (fire-and-forget to avoid blocking response)
71+
recomputeSkillScores(db, skill.id).catch((err) =>
72+
console.error("Failed to recompute scores after favorite toggle:", err)
73+
);
7274

7375
return Response.json({ favorited });
7476
} catch (error) {

0 commit comments

Comments
 (0)