From e67145493ec720a411d642a17d42be3bf7dee10c Mon Sep 17 00:00:00 2001 From: Arnaud Date: Wed, 8 Jul 2026 22:33:36 +0200 Subject: [PATCH] fix: rating not shown after refresh, comment reactions not shown until clicked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The interactive RatingStars widget was always initialized with initialScore={0}, ignoring the current user's existing rating. Fetch it via a third parallel query and pass it through — the rating was persisted correctly all along, just never displayed back. - CommentReactions never fetched its counts on mount, relying solely on initialCounts/initialUserReactions props that comments-section always passed as empty — so every comment showed zero reactions until you clicked one yourself (which returns fresh counts from the POST response). Fetch on mount via the existing GET endpoint. Co-Authored-By: Claude Sonnet 5 --- apps/web/app/(app)/recipes/[id]/page.tsx | 6 ++++-- apps/web/components/social/comment-reactions.tsx | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/web/app/(app)/recipes/[id]/page.tsx b/apps/web/app/(app)/recipes/[id]/page.tsx index 5c53a72..3f0a4e7 100644 --- a/apps/web/app/(app)/recipes/[id]/page.tsx +++ b/apps/web/app/(app)/recipes/[id]/page.tsx @@ -55,7 +55,7 @@ export default async function RecipePage({ params }: Params) { const VISIBILITY_LABEL = m.recipe.visibility; const DIETARY_LABELS = m.recipe.dietary; - const [recipe, ratingData, favoriteData] = await Promise.all([ + const [recipe, ratingData, favoriteData, myRating] = await Promise.all([ db.query.recipes.findFirst({ where: and( eq(recipes.id, id), @@ -70,6 +70,7 @@ export default async function RecipePage({ params }: Params) { }), db.select({ avgScore: avg(ratings.score), total: count() }).from(ratings).where(eq(ratings.recipeId, id)), db.query.favorites.findFirst({ where: and(eq(favorites.userId, session.user.id), eq(favorites.recipeId, id)) }), + db.query.ratings.findFirst({ where: and(eq(ratings.recipeId, id), eq(ratings.userId, session.user.id)) }), ]); if (!recipe) notFound(); @@ -79,6 +80,7 @@ export default async function RecipePage({ params }: Params) { const avgScore = ratingData[0]?.avgScore ? parseFloat(ratingData[0].avgScore) : null; const ratingCount = ratingData[0]?.total ?? 0; const isFavorited = !!favoriteData; + const myScore = myRating?.score ?? 0; const cover = recipe.photos.find((p) => p.isCover) ?? recipe.photos[0]; const totalMins = (recipe.prepMins ?? 0) + (recipe.cookMins ?? 0); @@ -384,7 +386,7 @@ export default async function RecipePage({ params }: Params) { <>
- +
diff --git a/apps/web/components/social/comment-reactions.tsx b/apps/web/components/social/comment-reactions.tsx index 7949929..79fe94f 100644 --- a/apps/web/components/social/comment-reactions.tsx +++ b/apps/web/components/social/comment-reactions.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { toast } from "sonner"; const REACTIONS: Record = { @@ -24,6 +24,20 @@ export function CommentReactions({ recipeId, commentId, initialCounts = {}, init const [userReactions, setUserReactions] = useState(initialUserReactions); const [pending, setPending] = useState(null); + useEffect(() => { + let cancelled = false; + fetch(`/api/v1/recipes/${recipeId}/comments/${commentId}/reactions`) + .then((res) => (res.ok ? res.json() : null)) + .then((data: { counts: Record; userReactions: string[] } | null) => { + if (!data || cancelled) return; + setCounts(data.counts); + setUserReactions(data.userReactions); + }) + .catch(() => {}); + return () => { cancelled = true; }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [recipeId, commentId]); + async function toggle(type: string) { if (pending) return; setPending(type);