fix lazy carousels

This commit is contained in:
Pas 2025-06-01 19:08:56 -06:00
parent b8ca66b4cb
commit bab39e2580
2 changed files with 96 additions and 78 deletions

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { get } from "@/backend/metadata/tmdb";
import { useIntersectionObserver } from "@/pages/discover/hooks/useIntersectionObserver";
@ -8,7 +8,6 @@ import { MediaItem } from "@/utils/mediaTypes";
import { MediaCarousel } from "./MediaCarousel";
import {
Category,
Genre,
Media,
Movie,
TVShow,
@ -17,39 +16,48 @@ import {
} from "../common";
interface LazyMediaCarouselProps {
category?: Category;
genre?: Genre;
mediaType: "movie" | "tv";
medias?: Media[];
category?: string;
isTVShow: boolean;
carouselRefs: React.MutableRefObject<{
[key: string]: HTMLDivElement | null;
}>;
onShowDetails?: (media: MediaItem) => void;
preloadedMedia?: Movie[] | TVShow[];
genreId?: number;
title?: string;
moreContent?: boolean;
moreLink?: string;
relatedButtons?: Array<{ name: string; id: string }>;
onButtonClick?: (id: string, name: string) => void;
moreContent?: boolean;
recommendationSources?: Array<{ id: string; title: string }>;
selectedRecommendationSource?: string;
onRecommendationSourceChange?: (id: string) => void;
preloadedMedia?: Movie[] | TVShow[];
title?: string;
}
export function LazyMediaCarousel({
medias: propMedias,
category,
genre,
mediaType,
isTVShow,
carouselRefs,
onShowDetails,
preloadedMedia,
genreId,
title,
moreContent,
moreLink,
relatedButtons,
onButtonClick,
moreContent,
recommendationSources,
selectedRecommendationSource,
onRecommendationSourceChange,
preloadedMedia,
title,
}: LazyMediaCarouselProps) {
const [medias, setMedias] = useState<Media[]>([]);
const [loading, setLoading] = useState(!preloadedMedia);
const [medias, setMedias] = useState<Media[]>(propMedias || []);
const [loading, setLoading] = useState(!preloadedMedia && !propMedias);
const hasLoaded = useRef(false);
const categoryData = (mediaType === "movie" ? categories : tvCategories).find(
(c: Category) => c.name === (category?.name || genre?.name || title || ""),
const categoryData = (isTVShow ? tvCategories : categories).find(
(c: Category) => c.name === (category || title || ""),
);
// Use intersection observer to detect when this component is visible
@ -57,12 +65,15 @@ export function LazyMediaCarousel({
{ rootMargin: "200px" }, // Load when within 200px of viewport
);
// Use the lazy loading hook only if we don't have preloaded media
// Use the lazy loading hook only if we don't have preloaded media or prop medias
// and haven't loaded yet
const { media } = useLazyTMDBData(
!preloadedMedia ? genre || null : null,
!preloadedMedia ? category || null : null,
mediaType,
isIntersecting,
null, // We don't use genre anymore since we're using category directly
!preloadedMedia && !propMedias && !hasLoaded.current
? categoryData || null
: null,
isTVShow ? "tv" : "movie",
isIntersecting && !hasLoaded.current,
);
// Update medias when data is loaded or preloaded
@ -70,15 +81,23 @@ export function LazyMediaCarousel({
if (preloadedMedia) {
setMedias(preloadedMedia);
setLoading(false);
hasLoaded.current = true;
} else if (propMedias) {
setMedias(propMedias);
setLoading(false);
hasLoaded.current = true;
} else if (media.length > 0) {
setMedias(media);
setLoading(false);
hasLoaded.current = true;
}
}, [media, preloadedMedia]);
}, [media, preloadedMedia, propMedias]);
// Only fetch category content if we don't have preloaded media
// Only fetch category content if we don't have preloaded media or prop medias
// and haven't loaded yet
useEffect(() => {
if (preloadedMedia || !categoryData) return;
if (preloadedMedia || propMedias || !categoryData || hasLoaded.current)
return;
const fetchContent = async () => {
try {
@ -87,6 +106,7 @@ export function LazyMediaCarousel({
language: "en-US",
});
setMedias(data.results);
hasLoaded.current = true;
} catch (error) {
console.error("Error fetching content:", error);
} finally {
@ -95,10 +115,10 @@ export function LazyMediaCarousel({
};
fetchContent();
}, [categoryData, preloadedMedia]);
}, [categoryData, preloadedMedia, propMedias]);
const categoryName = category?.name || genre?.name || title || "";
const categorySlug = `${categoryName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${mediaType}`;
const categoryName = category || title || "";
const categorySlug = `${categoryName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${isTVShow ? "tv" : "movie"}`;
if (loading) {
return (
@ -118,24 +138,23 @@ export function LazyMediaCarousel({
<MediaCarousel
medias={medias}
category={categoryName}
isTVShow={mediaType === "tv"}
isTVShow={isTVShow}
carouselRefs={carouselRefs}
onShowDetails={onShowDetails}
genreId={genreId}
relatedButtons={relatedButtons}
onButtonClick={onButtonClick}
moreContent={moreContent}
moreLink={
categoryData
? `/discover/more/category/${categoryData.urlPath}/${categoryData.mediaType}`
: undefined
}
moreLink={moreLink}
recommendationSources={recommendationSources}
selectedRecommendationSource={selectedRecommendationSource}
onRecommendationSourceChange={onRecommendationSourceChange}
/>
) : (
<div className="relative overflow-hidden carousel-container">
<div id={`carousel-${categorySlug}`}>
<h2 className="ml-2 md:ml-8 mt-2 text-2xl cursor-default font-bold text-white md:text-2xl mx-auto pl-5 text-balance">
{categoryName} {mediaType === "tv" ? "Shows" : "Movies"}
{categoryName} {isTVShow ? "Shows" : "Movies"}
</h2>
<div className="flex whitespace-nowrap pt-0 pb-4 overflow-auto scrollbar rounded-xl overflow-y-hidden h-[300px] animate-pulse bg-background-secondary/20">
<div className="w-full text-center flex items-center justify-center">

View file

@ -23,7 +23,6 @@ import { DiscoverNavigation } from "./components/DiscoverNavigation";
import type { FeaturedMedia } from "./components/FeaturedCarousel";
import { LazyMediaCarousel } from "./components/LazyMediaCarousel";
import { LazyTabContent } from "./components/LazyTabContent";
import { MediaCarousel } from "./components/MediaCarousel";
import { ScrollToTopButton } from "./components/ScrollToTopButton";
// Provider constants moved from DiscoverNavigation
@ -494,37 +493,13 @@ export function DiscoverContent() {
detailsModal.show();
};
// Render Editor Picks content
const renderEditorPicksContent = () => {
return (
<>
<LazyMediaCarousel
preloadedMedia={filteredGenreMovies}
title="Editor Picks"
mediaType="movie"
carouselRefs={carouselRefs}
onShowDetails={handleShowDetails}
moreContent
/>
<LazyMediaCarousel
preloadedMedia={filteredGenreTVShows}
title="Editor Picks"
mediaType="tv"
carouselRefs={carouselRefs}
onShowDetails={handleShowDetails}
moreContent
/>
</>
);
};
// Render Movies content with lazy loading
const renderMoviesContent = () => {
return (
<>
{/* Movie Recommendations */}
{movieRecommendations.length > 0 && (
<MediaCarousel
<LazyMediaCarousel
medias={movieRecommendations}
category={movieRecommendationTitle}
isTVShow={false}
@ -540,8 +515,8 @@ export function DiscoverContent() {
{/* In Cinemas */}
<LazyMediaCarousel
category={categories[0]}
mediaType="movie"
category={categories[0].name}
isTVShow={false}
carouselRefs={carouselRefs}
onShowDetails={handleShowDetails}
moreContent
@ -549,8 +524,8 @@ export function DiscoverContent() {
{/* Top Rated */}
<LazyMediaCarousel
category={categories[1]}
mediaType="movie"
category={categories[1].name}
isTVShow={false}
carouselRefs={carouselRefs}
onShowDetails={handleShowDetails}
moreContent
@ -558,15 +533,15 @@ export function DiscoverContent() {
{/* Popular */}
<LazyMediaCarousel
category={categories[2]}
mediaType="movie"
category={categories[2].name}
isTVShow={false}
carouselRefs={carouselRefs}
onShowDetails={handleShowDetails}
moreContent
/>
{/* Provider Movies */}
<MediaCarousel
<LazyMediaCarousel
medias={providerMovies}
category={`Movies on ${selectedProvider.name || ""}`}
isTVShow={false}
@ -582,7 +557,7 @@ export function DiscoverContent() {
/>
{/* Genre Movies */}
<MediaCarousel
<LazyMediaCarousel
medias={filteredGenreMovies}
category={`${selectedGenre.name || ""}`}
isTVShow={false}
@ -606,7 +581,7 @@ export function DiscoverContent() {
<>
{/* TV Show Recommendations */}
{tvRecommendations.length > 0 && (
<MediaCarousel
<LazyMediaCarousel
medias={tvRecommendations}
category={tvRecommendationTitle}
isTVShow
@ -622,8 +597,8 @@ export function DiscoverContent() {
{/* On Air */}
<LazyMediaCarousel
category={tvCategories[0]}
mediaType="tv"
category={tvCategories[0].name}
isTVShow
carouselRefs={carouselRefs}
onShowDetails={handleShowDetails}
moreContent
@ -631,8 +606,8 @@ export function DiscoverContent() {
{/* Top Rated */}
<LazyMediaCarousel
category={tvCategories[1]}
mediaType="tv"
category={tvCategories[1].name}
isTVShow
carouselRefs={carouselRefs}
onShowDetails={handleShowDetails}
moreContent
@ -640,15 +615,15 @@ export function DiscoverContent() {
{/* Popular */}
<LazyMediaCarousel
category={tvCategories[2]}
mediaType="tv"
category={tvCategories[2].name}
isTVShow
carouselRefs={carouselRefs}
onShowDetails={handleShowDetails}
moreContent
/>
{/* Provider TV Shows */}
<MediaCarousel
<LazyMediaCarousel
medias={providerTVShows}
category={`Shows on ${selectedProvider.name || ""}`}
isTVShow
@ -664,7 +639,7 @@ export function DiscoverContent() {
/>
{/* Genre TV Shows */}
<MediaCarousel
<LazyMediaCarousel
medias={filteredGenreTVShows}
category={`${selectedGenre.name || ""}`}
isTVShow
@ -682,6 +657,30 @@ export function DiscoverContent() {
);
};
// Render Editor Picks content
const renderEditorPicksContent = () => {
return (
<>
<LazyMediaCarousel
preloadedMedia={filteredGenreMovies}
title="Editor Picks"
isTVShow={false}
carouselRefs={carouselRefs}
onShowDetails={handleShowDetails}
moreContent
/>
<LazyMediaCarousel
preloadedMedia={filteredGenreTVShows}
title="Editor Picks"
isTVShow
carouselRefs={carouselRefs}
onShowDetails={handleShowDetails}
moreContent
/>
</>
);
};
return (
<div className="relative min-h-screen">
<DiscoverNavigation