From d0e3121391825a26325b1f12cd29dcd7cf337a1b Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Tue, 23 Jun 2026 13:38:03 +0200 Subject: [PATCH] chore: lazy loading --- src/pages/Home.tsx | 130 +++++++++++++++++++++++++++++---------------- 1 file changed, 84 insertions(+), 46 deletions(-) diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 0ae93c0..51b9e1c 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -18,39 +18,67 @@ const Home = () => { const [topPaid, setTopPaid] = useState(homeCache?.topPaid || []); const [popular, setPopular] = useState(homeCache?.popular || []); const [newest, setNewest] = useState(homeCache?.newest || []); - const [loading, setLoading] = useState(!homeCache); - const hasFetched = useRef(!!homeCache); + // Load sections lazily when the user scrolls them into view + const [loading, setLoading] = useState(false); - useEffect(() => { - if (hasFetched.current) return; - - const fetchSections = async () => { - try { - const [topPaidRes, popularRes, newestRes] = await Promise.all([ - api.get('/plugins', { params: { type: 'paid', sort: 'downloads', limit: 4 } }), - api.get('/plugins', { params: { sort: 'downloads', limit: 8 } }), - api.get('/plugins', { params: { sort: 'newest', limit: 12 } }) - ]); + const topPaidRef = useRef(null); + const popularRef = useRef(null); + const newestRef = useRef(null); - const data = { - topPaid: Array.isArray(topPaidRes.data) ? topPaidRes.data : [], - popular: Array.isArray(popularRes.data) ? popularRes.data : [], - newest: Array.isArray(newestRes.data) ? newestRes.data : [] - }; + const fetched = useRef({ topPaid: !!homeCache?.topPaid, popular: !!homeCache?.popular, newest: !!homeCache?.newest }); - setTopPaid(data.topPaid); - setPopular(data.popular); - setNewest(data.newest); - homeCache = data; - hasFetched.current = true; - } catch (err) { - console.error('Fetch error:', err); - } finally { - setLoading(false); - } + const saveSection = (key: 'topPaid' | 'popular' | 'newest', data: any[]) => { + if (key === 'topPaid') setTopPaid(data); + if (key === 'popular') setPopular(data); + if (key === 'newest') setNewest(data); + + homeCache = { + topPaid: key === 'topPaid' ? data : homeCache?.topPaid || [], + popular: key === 'popular' ? data : homeCache?.popular || [], + newest: key === 'newest' ? data : homeCache?.newest || [] }; - fetchSections(); + fetched.current[key] = true; + }; + + useEffect(() => { + if (fetched.current.topPaid && fetched.current.popular && fetched.current.newest) return; + + const observers: IntersectionObserver[] = []; + + const createObserver = (el: HTMLElement | null, key: 'topPaid' | 'popular' | 'newest', params: any) => { + if (!el || fetched.current[key]) return; + const obs = new IntersectionObserver(async (entries, observer) => { + for (const entry of entries) { + if (entry.isIntersecting) { + fetched.current[key] = true; // prevent duplicate triggers + try { + setLoading(true); + const res = await api.get('/plugins', { params }); + const list = Array.isArray(res.data) ? res.data : []; + saveSection(key, list); + } catch (err) { + console.error('Fetch error for', key, err); + } finally { + setLoading(false); + observer.disconnect(); + } + } + } + }, { rootMargin: '200px' }); + + obs.observe(el); + observers.push(obs); + }; + + // Ensure refs are attached before creating observers + requestAnimationFrame(() => { + createObserver(topPaidRef.current, 'topPaid', { type: 'paid', sort: 'downloads', limit: 4 }); + createObserver(popularRef.current, 'popular', { sort: 'downloads', limit: 8 }); + createObserver(newestRef.current, 'newest', { sort: 'newest', limit: 12 }); + }); + + return () => observers.forEach(o => o.disconnect()); }, []); if (loading) { @@ -75,36 +103,46 @@ const Home = () => {
- {topPaid.length > 0 && ( -
-

Top Premium

-
- {topPaid.map((plugin) => ( +
+

Top Premium

+
+ {topPaid.length > 0 ? ( + topPaid.map((plugin) => ( - ))} -
-
- )} + )) + ) : ( +
{loading ? 'Loading premium plugins...' : 'Scroll to load premium plugins'}
+ )} +
+
-
+

Most Popular

- {popular.map((plugin) => ( - - ))} + {popular.length > 0 ? ( + popular.map((plugin) => ( + + )) + ) : ( +
{loading ? 'Loading popular plugins...' : 'Scroll to load popular plugins'}
+ )}
-
+

New & Trending

- {newest.map((plugin) => ( - - ))} + {newest.length > 0 ? ( + newest.map((plugin) => ( + + )) + ) : ( +
{loading ? 'Loading new plugins...' : 'Scroll to load new plugins'}
+ )}
- {topPaid.length === 0 && popular.length === 0 && newest.length === 0 && ( + {(fetched.current?.topPaid && fetched.current?.popular && fetched.current?.newest && topPaid.length === 0 && popular.length === 0 && newest.length === 0) && (

No plugins found. Check back later!