From cb8365aff64a453f9523052405d07298affa938a Mon Sep 17 00:00:00 2001 From: Juanma Hidalgo Date: Mon, 3 Feb 2025 10:49:40 +0100 Subject: [PATCH] feat: add intersection observer to homepage sections (#2361) --- webapp/src/components/HomePage/HomePage.tsx | 63 ++++++++++++++------- webapp/src/components/HomePage/hooks.ts | 43 ++++++++++++++ 2 files changed, 87 insertions(+), 19 deletions(-) create mode 100644 webapp/src/components/HomePage/hooks.ts diff --git a/webapp/src/components/HomePage/HomePage.tsx b/webapp/src/components/HomePage/HomePage.tsx index 21fc79c45..36a82e8a8 100644 --- a/webapp/src/components/HomePage/HomePage.tsx +++ b/webapp/src/components/HomePage/HomePage.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo } from 'react' +import React, { useCallback, useMemo, useRef } from 'react' import { useHistory } from 'react-router-dom' import { Banner } from 'decentraland-dapps/dist/containers/Banner' import { getAnalytics } from 'decentraland-dapps/dist/modules/analytics/utils' @@ -19,6 +19,7 @@ import { NavigationTab } from '../Navigation/Navigation.types' import { PageLayout } from '../PageLayout' import { RankingsTable } from '../RankingsTable' import { RecentlySoldTable } from '../RecentlySoldTable' +import { useIntersectionObserver } from './hooks' import { Slideshow } from './Slideshow' import { Props } from './HomePage.types' import './HomePage.css' @@ -162,31 +163,55 @@ const HomePage = (props: Props) => { [sections, fetchAssetsForView] ) - useEffect(() => { - let view: HomepageView - for (view in homepage) { - fetchAssetsForView(view) + // Create a Map to store refs for each view section + const sectionRefs = useRef(new Map()) + + // Custom hook to handle intersection observer + const onIntersect = useCallback( + (view: HomepageView) => { + // Only fetch if we don't already have data for this view + const isLoadingView = homepageLoading[view] + if ((!homepage[view] || homepage[view].length === 0) && !isLoadingView) { + fetchAssetsForView(view) + } + }, + [fetchAssetsForView, homepage, homepageLoading] + ) + + useIntersectionObserver({ + refs: sectionRefs.current, + onIntersect, + options: { + rootMargin: '100px', + threshold: 0.1 } - // eslint-disable-next-line - }, [fetchAssetsForView]) + }) const renderSlideshow = (view: HomepageView) => { const hasItemsSection = view === View.HOME_NEW_ITEMS || view === View.HOME_WEARABLES return ( - { + if (element) { + sectionRefs.current.set(view, element) + } + }} key={view} - view={view} - title={t(`home_page.${view}`)} - subtitle={sectionsSubtitles[view]} - viewAllTitle={sectionsViewAllTitle[view]} - emptyMessage={sectionsEmptyMessages[view]} - assets={homepageLoading[view] ? [] : homepage[view]} - hasItemsSection={hasItemsSection} - isLoading={homepageLoading[view]} - onViewAll={() => handleViewAll(view)} - onChangeItemSection={hasItemsSection ? handleOnChangeItemSection : undefined} - /> + > + handleViewAll(view)} + onChangeItemSection={hasItemsSection ? handleOnChangeItemSection : undefined} + /> + ) } diff --git a/webapp/src/components/HomePage/hooks.ts b/webapp/src/components/HomePage/hooks.ts new file mode 100644 index 000000000..d933ea010 --- /dev/null +++ b/webapp/src/components/HomePage/hooks.ts @@ -0,0 +1,43 @@ +import { useEffect } from 'react' +import { HomepageView } from '../../modules/ui/asset/homepage/types' + +type IntersectionObserverProps = { + refs: Map + onIntersect: (view: HomepageView) => void + options?: IntersectionObserverInit +} + +export const useIntersectionObserver = ({ refs, onIntersect, options = {} }: IntersectionObserverProps) => { + useEffect(() => { + // Keep track of which sections have been loaded + const loadedSections = new Set() + + const observer = new IntersectionObserver( + entries => { + entries.forEach(entry => { + // Find which view this element corresponds to + const view = Array.from(refs.entries()).find(([_, element]) => element === entry.target)?.[0] + + if (view && entry.isIntersecting && !loadedSections.has(view)) { + loadedSections.add(view) + onIntersect(view) + } + }) + }, + { + rootMargin: '100px', + threshold: 0.1, + ...options + } + ) + + // Observe all section refs + refs.forEach(element => { + observer.observe(element) + }) + + return () => { + observer.disconnect() + } + }, [refs, onIntersect, options]) +}