Skip to content

Commit 74c7ec3

Browse files
committed
refactor: Simplify code for loading next recipe in FYP
1 parent 60fc4e2 commit 74c7ec3

File tree

2 files changed

+34
-58
lines changed

2 files changed

+34
-58
lines changed

frontend/app/protected/fyp/page.tsx

Lines changed: 19 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ import { toast } from "sonner";
88

99
export default function FYP() {
1010
const { data: session } = useSession();
11-
const [currentRecipe, setCurrentRecipe] = useState<Recipe | null>(null);
1211
const [loading, setLoading] = useState(true);
12+
const [currentRecipe, setCurrentRecipe] = useState<Recipe | null>(null);
1313
const [recommendations, setRecommendations] = useState<Recipe[]>([]); // This array is treated as a queue
14-
const [swipedRecipeId, setSwipedRecipeId] = useState<string | null>(null); // Track the swiped recipe id instead of removing from the queue so that the animation can complete
15-
const [savedRecipeIds, setSavedRecipeIds] = useState<Set<string>>(new Set()); // Track saved recipe IDs
14+
const [savedRecipeIds, setSavedRecipeIds] = useState<Set<string>>(new Set());
1615

1716
// Utility function to preload images
1817
const preloadImages = useCallback((recipes: Recipe[]) => {
@@ -27,26 +26,6 @@ export default function FYP() {
2726
});
2827
}, []);
2928

30-
// Current recipe should always be the first non-swiped recipe in the recommendations array
31-
useEffect(() => {
32-
if (recommendations.length > 0) {
33-
const firstNonSwiped = recommendations.find(
34-
(recipe) => recipe.id !== swipedRecipeId,
35-
);
36-
setCurrentRecipe(firstNonSwiped || null);
37-
38-
// Trigger immediate preloading of next images when current recipe changes
39-
if (firstNonSwiped) {
40-
// Force a re-render to trigger preloading
41-
setTimeout(() => {
42-
setCurrentRecipe(firstNonSwiped);
43-
}, 0);
44-
}
45-
} else {
46-
setCurrentRecipe(null);
47-
}
48-
}, [recommendations, swipedRecipeId]);
49-
5029
const fetchRecommendations = async () => {
5130
if (!session?.user?.id) {
5231
setLoading(false);
@@ -138,28 +117,13 @@ export default function FYP() {
138117
const handleSwipe = async (swipe: "like" | "dislike") => {
139118
if (!currentRecipe) return;
140119

141-
// Capture the recipe ID and title before any state changes
142-
const recipeId = currentRecipe.id;
143-
const recipeTitle = currentRecipe.title;
144-
145-
// Mark the recipe as swiped to hide it from the current recipe selection
146-
setSwipedRecipeId(recipeId);
147-
148120
// Submit feedback to backend in the background
149121
try {
150-
await submitFeedback(recipeId, swipe);
151-
// Remove the recipe from the array after backend submission
152-
setRecommendations((prev) => {
153-
return prev.filter((recipe) => recipe.id !== recipeId);
154-
});
155-
// Clear the swiped recipe ID
156-
setSwipedRecipeId(null);
122+
await submitFeedback(currentRecipe.id, swipe);
157123
} catch (error) {
158124
toast.error("Error", {
159125
description: "Failed to submit feedback",
160126
});
161-
// If backend submission fails, unmark the recipe as swiped
162-
setSwipedRecipeId(null);
163127
}
164128
};
165129

@@ -208,23 +172,31 @@ export default function FYP() {
208172
}
209173
};
210174

175+
// Pass a seperate function to the Recipe Card to handle the next recipe because it must be called after the
176+
// animation completes
177+
const handleNextRecipe = (latest: "like" | "dislike") => {
178+
setRecommendations((prev) => {
179+
return prev.filter((recipe) => recipe.id !== currentRecipe?.id);
180+
});
181+
};
182+
211183
useEffect(() => {
212184
if (session?.user?.id) {
213185
fetchRecommendations();
214186
fetchSavedRecipes();
215187
}
216188
}, [session?.user?.id]);
217189

218-
// Preload images whenever recommendations change
190+
// Current recipe should always be the first non-swiped recipe in the recommendations array
191+
// Preload images for the next 5 recipes
219192
useEffect(() => {
220193
if (recommendations.length > 0) {
221-
// Preload the next few recipes that aren't swiped
222-
const recipesToPreload = recommendations
223-
.filter((recipe) => recipe.id !== swipedRecipeId)
224-
.slice(0, 5); // Preload next 5 recipes
225-
preloadImages(recipesToPreload);
194+
setCurrentRecipe(recommendations[0]);
195+
preloadImages(recommendations.slice(0, 5));
196+
} else {
197+
setCurrentRecipe(null);
226198
}
227-
}, [recommendations, swipedRecipeId, preloadImages]);
199+
}, [recommendations, preloadImages]);
228200

229201
if (loading) {
230202
return (
@@ -257,6 +229,7 @@ export default function FYP() {
257229
recipe={currentRecipe}
258230
onSwipe={handleSwipe}
259231
onBookmark={handleBookmark}
232+
onAnimationComplete={handleNextRecipe}
260233
isSaved={currentRecipe ? savedRecipeIds.has(currentRecipe.id) : false}
261234
/>
262235
</div>

frontend/components/fyp-recipe-card.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,19 @@ interface FYPRecipeCardProps {
2020
recipe: Recipe;
2121
onSwipe: (swipe: "like" | "dislike") => void;
2222
onBookmark: () => void;
23+
onAnimationComplete: (latest: "like" | "dislike") => void;
2324
isSaved?: boolean;
2425
}
2526

2627
export function FYPRecipeCard({
2728
recipe,
2829
onSwipe,
2930
onBookmark,
31+
onAnimationComplete,
3032
isSaved = false,
3133
}: FYPRecipeCardProps) {
3234
const [expanded, setExpanded] = useState(false);
33-
const [isAnimating, setIsAnimating] = useState(false);
35+
const [isAnimating, setIsAnimating] = useState(false); // Tracks when to disable feedback buttons
3436
const [swipeDirection, setSwipeDirection] = useState<
3537
"like" | "dislike" | null
3638
>(null);
@@ -39,16 +41,15 @@ export function FYPRecipeCard({
3941
if (isAnimating) return;
4042
setIsAnimating(true);
4143
setSwipeDirection(action);
44+
onSwipe(action); // Sends feedback to backend. Showing the next recipe is handled separately after anim is done
45+
};
4246

43-
// Call onSwipe immediately to mark recipe as swiped
44-
onSwipe(action);
45-
46-
// Reset animation state after animation completes
47-
setTimeout(() => {
48-
setIsAnimating(false);
49-
setSwipeDirection(null);
50-
setExpanded(false);
51-
}, 300);
47+
// Trigger backend function to remove the recipe from the recommendations array and load the next recipe
48+
const handleAnimationComplete = (latest: "like" | "dislike") => {
49+
setIsAnimating(false);
50+
setSwipeDirection(null);
51+
setExpanded(false);
52+
onAnimationComplete(latest);
5253
};
5354

5455
const slideVariants = {
@@ -68,15 +69,17 @@ export function FYPRecipeCard({
6869
};
6970

7071
return (
71-
<div className="flex min-h-[calc(100vh-8rem)] flex-col items-center justify-center">
72+
<div
73+
className={`flex min-h-[calc(100vh-8rem)] flex-col items-center justify-center`}
74+
>
7275
<AnimatePresence mode="wait">
7376
<motion.div
7477
key={recipe.id}
7578
className="relative mx-auto w-full max-w-sm"
7679
variants={slideVariants}
7780
initial="initial"
7881
animate={swipeDirection || "initial"}
79-
exit={swipeDirection === "like" ? "like" : "dislike"}
82+
onAnimationComplete={() => handleAnimationComplete(swipeDirection!)}
8083
>
8184
{/* Swipe overlay indicators */}
8285
<AnimatePresence>

0 commit comments

Comments
 (0)