From 6f2c294257363cdc26ff309d5ba1cf82051b5334 Mon Sep 17 00:00:00 2001 From: Chris-1010 <122332721@umail.ucc.ie> Date: Thu, 27 Feb 2025 01:25:38 +0000 Subject: [PATCH] FIX: `FetchOnScroll` functionality on `AllCategoriesPage` - BUG: duplicate categories appear --- frontend/src/hooks/fetchContentOnScroll.ts | 17 +++-- frontend/src/pages/AllCategoriesPage.tsx | 76 ++++++++++++---------- 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/frontend/src/hooks/fetchContentOnScroll.ts b/frontend/src/hooks/fetchContentOnScroll.ts index 520dac3..d11a5c5 100644 --- a/frontend/src/hooks/fetchContentOnScroll.ts +++ b/frontend/src/hooks/fetchContentOnScroll.ts @@ -2,20 +2,29 @@ import { useEffect } from "react"; export function fetchContentOnScroll(callback: () => void, hasMoreData: boolean) { useEffect(() => { + const root = document.querySelector("#root") as HTMLElement; + const handleScroll = () => { if (!hasMoreData) return; // Don't trigger scroll if no more data - const scrollPosition = window.innerHeight + document.documentElement.scrollTop; - const scrollHeight = document.documentElement.scrollHeight; + + // Use properties of the element itself, not document + const scrollPosition = root.scrollTop + root.clientHeight; + const scrollHeight = root.scrollHeight; if (scrollPosition >= scrollHeight * 0.9) { callback(); // Trigger data fetching when 90% scroll is reached + setTimeout(() => { + // Delay to prevent multiple fetches + root.scrollTop = root.scrollTop - 1; + }, 100); } }; - window.addEventListener("scroll", handleScroll); + // Add scroll event listener to the root element + root.addEventListener("scroll", handleScroll); return () => { - window.removeEventListener("scroll", handleScroll); // Cleanup on unmount + root.removeEventListener("scroll", handleScroll); // Cleanup on unmount }; }, [callback, hasMoreData]); } \ No newline at end of file diff --git a/frontend/src/pages/AllCategoriesPage.tsx b/frontend/src/pages/AllCategoriesPage.tsx index d75aaa2..6ad6689 100644 --- a/frontend/src/pages/AllCategoriesPage.tsx +++ b/frontend/src/pages/AllCategoriesPage.tsx @@ -1,101 +1,109 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useRef, useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import ListRow from "../components/Layout/ListRow"; import DynamicPageContent from "../components/Layout/DynamicPageContent"; import { fetchContentOnScroll } from "../hooks/fetchContentOnScroll"; import LoadingScreen from "../components/Layout/LoadingScreen"; +import { CategoryType } from "../types/CategoryType"; -interface CategoryData { - type: "category"; - id: number; - title: string; - viewers: number; - thumbnail: string; -} const AllCategoriesPage: React.FC = () => { - const [categories, setCategories] = useState([]); const navigate = useNavigate(); + const [allCategories, setAllCategories] = useState([]); const [categoryOffset, setCategoryOffset] = useState(0); - const [noCategories, setNoCategories] = useState(12); const [hasMoreData, setHasMoreData] = useState(true); const [isLoading, setIsLoading] = useState(true); const listRowRef = useRef(null); const fetchCategories = async () => { - if (isLoading) return; - + if (isLoading && categoryOffset > 0) return []; + try { + console.log(`Fetching categories with offset: ${categoryOffset}`); const response = await fetch( - `/api/categories/popular/${noCategories}/${categoryOffset}` + `/api/categories/popular/12/${categoryOffset}` ); - if (!response.ok) { - throw new Error("Failed to fetch categories"); - } + if (!response.ok) throw new Error("Failed to fetch categories"); + const data = await response.json(); + console.log("Categories fetched:", data.length); if (data.length === 0) { setHasMoreData(false); return []; } - setCategoryOffset((prev) => prev + data.length); + setCategoryOffset(categoryOffset + data.length); - const processedCategories = data.map((category: any) => ({ + const newCategories = data.map((category: any) => ({ type: "category" as const, id: category.category_id, title: category.category_name, - viewers: category.num_viewers, + viewers: category.num_viewers || 0, thumbnail: `/images/category_thumbnails/${category.category_name .toLowerCase() .replace(/ /g, "_")}.webp`, })); - setCategories((prev) => [...prev, ...processedCategories]); - return processedCategories; - } catch (error) { - console.error("Error fetching categories:", error); + return newCategories; + } catch (err) { + console.error("Error fetching categories:", err); + setHasMoreData(false); return []; } finally { setIsLoading(false); } }; + // Initial load useEffect(() => { - fetchCategories(); + const initialLoad = async () => { + const initialCategories = await fetchCategories(); + setAllCategories(initialCategories); + }; + + initialLoad(); }, []); - const loadOnScroll = async () => { - if (hasMoreData && listRowRef.current) { - const newCategories = await fetchCategories(); - if (newCategories?.length > 0) { + const loadMoreCategories = async () => { + if (!hasMoreData || (isLoading && categoryOffset > 0)) return; + + const newCategories = await fetchCategories(); + if (newCategories.length > 0) { + setAllCategories(prev => [...prev, ...newCategories]); + if (listRowRef.current && listRowRef.current.addMoreItems) { listRowRef.current.addMoreItems(newCategories); } } }; - fetchContentOnScroll(loadOnScroll, hasMoreData); - - if (hasMoreData && !categories.length) return ; + // Set up infinite scroll + fetchContentOnScroll(loadMoreCategories, hasMoreData); const handleCategoryClick = (categoryName: string) => { - console.log(categoryName); navigate(`/category/${categoryName}`); }; + if (isLoading && allCategories.length === 0) return ; + return ( + {!hasMoreData && allCategories.length > 0 && ( +
+ No more categories to load +
+ )}
); }; -export default AllCategoriesPage; +export default AllCategoriesPage; \ No newline at end of file