grain.social is a photo sharing platform built on atproto. grain.social
atproto photography appview
47
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: prevent infinite scroll from looping by guarding on fetch state

The IntersectionObserver callback now checks !isFetching before calling
fetchNextPage, preventing repeated requests while a fetch is in progress.
Sentinel stays mounted; no unmount/remount tricks needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+6 -9
+2 -5
app/lib/actions/infinite-scroll.ts
··· 1 - /** Svelte action: calls `onIntersect` when the element scrolls into view. */ 1 + /** Svelte action: calls `onIntersect` when the element scrolls into view. 2 + * The callback should guard against re-entry (e.g. check `!isFetchingNextPage`). */ 2 3 export function infiniteScroll(node: HTMLElement, onIntersect: () => void) { 3 4 const observer = new IntersectionObserver( 4 5 (entries) => { 5 6 if (entries[0]?.isIntersecting) { 6 7 onIntersect() 7 - // Re-observe so it fires again if the sentinel stays in viewport 8 - // (e.g. when a page returns too few items to scroll it out of view) 9 - observer.unobserve(node) 10 - requestAnimationFrame(() => observer.observe(node)) 11 8 } 12 9 }, 13 10 { rootMargin: '200px' },
+1 -1
app/lib/components/molecules/StoryArchive.svelte
··· 42 42 </div> 43 43 44 44 {#if archive.hasNextPage} 45 - <div use:infiniteScroll={() => archive.fetchNextPage()} class="sentinel"> 45 + <div use:infiniteScroll={() => { if (!archive.isFetchingNextPage) archive.fetchNextPage() }} class="sentinel"> 46 46 {#if archive.isFetchingNextPage}<Spinner />{/if} 47 47 </div> 48 48 {/if}
+1 -1
app/lib/components/organisms/FeedList.svelte
··· 107 107 /> 108 108 109 109 {#if cursor} 110 - <div use:infiniteScroll={loadMore} class="sentinel"> 110 + <div use:infiniteScroll={() => { if (!loadingMore) loadMore() }} class="sentinel"> 111 111 {#if loadingMore}<Spinner />{/if} 112 112 </div> 113 113 {/if}
+1 -1
app/lib/components/organisms/GalleryGrid.svelte
··· 71 71 {/each} 72 72 </div> 73 73 {#if hasMore} 74 - <div use:infiniteScroll={() => onLoadMore?.()} class="sentinel"> 74 + <div use:infiniteScroll={() => { if (!loadingMore) onLoadMore?.() }} class="sentinel"> 75 75 {#if loadingMore}<Spinner />{/if} 76 76 </div> 77 77 {/if}
+1 -1
app/routes/notifications/+page.svelte
··· 71 71 <NotificationItem {notif} /> 72 72 {/each} 73 73 {#if hasMore} 74 - <div use:infiniteScroll={loadMore} class="sentinel"> 74 + <div use:infiniteScroll={() => { if (!loadingMore) loadMore() }} class="sentinel"> 75 75 {#if loadingMore}<Spinner />{/if} 76 76 </div> 77 77 {/if}