import { useEffect, useRef, useState, type ReactNode } from 'react'; /** * Defer mounting `children` until the wrapper scrolls near the viewport. * * Long grids (e.g. the marketplace) otherwise mount every card — and every * hook/query/DOM node each one pulls in — up front. With this, off-screen cards * stay as a cheap placeholder until they're ~600px away, then mount once and * stay mounted. Falls back to immediate render when IntersectionObserver is * unavailable. */ export function LazyMount({ children, minHeight = 300, rootMargin = '600px', className, }: { children: ReactNode; /** Reserve height so the page doesn't jump when the card mounts. */ minHeight?: number; rootMargin?: string; className?: string; }) { const ref = useRef(null); const [visible, setVisible] = useState(false); useEffect(() => { if (visible) return; const el = ref.current; if (!el) return; if (typeof IntersectionObserver === 'undefined') { setVisible(true); return; } const observer = new IntersectionObserver( (entries) => { if (entries.some((e) => e.isIntersecting)) { setVisible(true); observer.disconnect(); } }, { rootMargin }, ); observer.observe(el); return () => observer.disconnect(); }, [visible, rootMargin]); return (
{visible ? children : null}
); }