Review

Auto-page vinyl crate dig with sentinel observer

CrateDig buyers were hammering Load more while flipping new pressings in the marketplace crate. Replace the button with a bottom sentinel watched by IntersectionObserver, use rootMargin so the next page prefetches before the footer enters view, and disconnect on cleanup.

ReactTier 1reactintersection-observerinfinite-scroll

Click a line to flag it, pick one or more labels, then submit. If the change looks correct, approve it.

components/market/CrateBrowser.tsx+16-5
1818export function CrateBrowser({ pressings, hasMore, loading, onLoadMore }: Props) {
19+ const sentinelRef = useRef<HTMLDivElement>(null);
1920
21+ useEffect(() => {
22+ const el = sentinelRef.current;
23+ if (!el || !hasMore) return;
24+ const io = new IntersectionObserver(
25+ ([entry]) => {
26+ if (entry.isIntersecting && !loading) onLoadMore();
27+ },
28+ // Prefetch the next crate page ~240px before the sentinel hits the viewport
29+ { root: null, rootMargin: "240px 0px", threshold: 0 },
30+ );
31+ io.observe(el);
32+ return () => io.disconnect();
33+ }, [hasMore, loading, onLoadMore]);
2034
2135 return (
2236 <div className="crate-grid">
2337 {pressings.map((p) => (
2438 <PressingCard key={p.id} pressing={p} />
2539 ))}
26- {hasMore && (
27- <button type="button" disabled={loading} onClick={onLoadMore}>
28- {loading ? "Spinning…" : "Load more"}
29- </button>
30- )}
40+ {hasMore && <div ref={sentinelRef} className="crate-sentinel" aria-hidden />}
41+ {loading && <p className="crate-status">Spinning the next crate…</p>}
3142 </div>
3243 );
3344}