Skip to content

Commit 49ad790

Browse files
authored
Improve <InfiniteScroll> cleanup after navigating away (#2610)
* wip * Update InfiniteScroll.svelte * test * Update server.js * More fixes + prefetch test
1 parent ea4475f commit 49ad790

File tree

16 files changed

+226
-36
lines changed

16 files changed

+226
-36
lines changed

packages/core/src/infiniteScroll.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,10 @@ export default function useInfiniteScroll(options: UseInfiniteScrollOptions): Us
9191
return {
9292
dataManager,
9393
elementManager,
94+
flush: () => {
95+
dataManager.removeEventListener()
96+
elementManager.flushAll()
97+
queryStringManager.cancel()
98+
},
9499
}
95100
}

packages/core/src/infiniteScroll/data.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const useInfiniteScrollData = (options: {
3535
const { previousPage, nextPage, currentPage: lastLoadedPage } = getScrollPropFromCurrentPage()
3636

3737
const state = {
38+
component: currentPage.get().component,
3839
loading: false,
3940
previousPage,
4041
nextPage,
@@ -54,7 +55,12 @@ export const useInfiniteScrollData = (options: {
5455
state.requestCount = rememberedState.requestCount || 0
5556
}
5657

57-
const removeEventListener = router.on('success', () => {
58+
const removeEventListener = router.on('success', (event) => {
59+
if (state.component !== event.detail.page.component) {
60+
// Only reset state if it's the same component
61+
return
62+
}
63+
5864
const scrollProp = getScrollPropFromCurrentPage()
5965

6066
if (scrollProp.reset) {

packages/core/src/infiniteScroll/elements.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,8 @@ export const useInfiniteScrollElementManager = (options: {
5757

5858
// Track individual items entering/leaving viewport for URL synchronization
5959
// When items become visible, we update the URL to reflect the current page
60-
itemsObserver = intersectionObservers.new(
61-
(entry: IntersectionObserverEntry) => options.onItemIntersected(entry.target as HTMLElement),
62-
{ threshold: 0 },
60+
itemsObserver = intersectionObservers.new((entry: IntersectionObserverEntry) =>
61+
options.onItemIntersected(entry.target as HTMLElement),
6362
)
6463

6564
// Set up trigger zones at start/end that load more content when intersected. The rootMargin
@@ -111,6 +110,7 @@ export const useInfiniteScrollElementManager = (options: {
111110
}
112111

113112
const flushAll = () => {
113+
disableTriggers()
114114
intersectionObservers.flushAll()
115115
itemsMutationObserver?.disconnect()
116116
}

packages/core/src/infiniteScroll/queryString.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,19 @@ export const useInfiniteScrollQueryString = (options: {
1313
getItemsElement: () => HTMLElement
1414
shouldPreserveUrl: () => boolean
1515
}) => {
16+
let enabled = true
17+
1618
// Debounced to avoid excessive URL updates during fast scrolling
1719
const onItemIntersected = debounce((itemElement: HTMLElement) => {
18-
if (options.shouldPreserveUrl() || !itemElement) {
20+
const itemsElement = options.getItemsElement()
21+
22+
if (!enabled || options.shouldPreserveUrl() || !itemElement || !itemsElement) {
1923
return
2024
}
2125

2226
// Count how many items from each page are currently visible in the viewport
2327
const pageMap = new Map<string, number>()
24-
const elements = [...options.getItemsElement().children] as HTMLElement[]
28+
const elements = [...itemsElement.children] as HTMLElement[]
2529

2630
getElementsInViewportFromCollection(itemElement, elements).forEach((element) => {
2731
const page = getPageFromElement(element) ?? '1'
@@ -60,5 +64,6 @@ export const useInfiniteScrollQueryString = (options: {
6064

6165
return {
6266
onItemIntersected,
67+
cancel: () => (enabled = false),
6368
}
6469
}

packages/core/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ export interface UseInfiniteScrollElementManager {
550550
export interface UseInfiniteScrollProps {
551551
dataManager: UseInfiniteScrollDataManager
552552
elementManager: UseInfiniteScrollElementManager
553+
flush: () => void
553554
}
554555

555556
export interface InfiniteScrollSlotProps {

packages/react/src/InfiniteScroll.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,7 @@ const InfiniteScroll = forwardRef<InfiniteScrollRef, ComponentProps>(
212212
}
213213

214214
return () => {
215-
dataManager.removeEventListener()
216-
elementManager.flushAll()
215+
infiniteScrollInstance.flush()
217216
setInfiniteScroll(null)
218217
}
219218
}, [data, resolvedItemsElement, resolvedStartElement, resolvedEndElement, scrollableParent])
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { InfiniteScroll, Link } from '@inertiajs/react'
2+
import UserCard, { User } from './UserCard'
3+
4+
export default ({ users }: { users: { data: User[] } }) => {
5+
return (
6+
<div>
7+
<Link href="/infinite-scroll">Go back to Links</Link>
8+
<InfiniteScroll
9+
data="users"
10+
style={{ display: 'grid', gap: '20px' }}
11+
loading={() => <div style={{ textAlign: 'center', padding: '20px' }}>Loading...</div>}
12+
>
13+
{users.data.map((user) => (
14+
<UserCard key={user.id} user={user} />
15+
))}
16+
</InfiniteScroll>
17+
</div>
18+
)
19+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Link } from '@inertiajs/react'
2+
3+
export default () => {
4+
return (
5+
<div>
6+
<Link href="/infinite-scroll-with-link">Go to InfiniteScrollWithLink</Link>
7+
<Link href="/infinite-scroll-with-link" prefetch>
8+
Go to InfiniteScrollWithLink (Prefetch)
9+
</Link>
10+
</div>
11+
)
12+
}

packages/svelte/src/InfiniteScroll.svelte

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,7 @@
182182
: infiniteScrollInstance?.elementManager.disableTriggers()
183183
}
184184
185-
onDestroy(() => {
186-
infiniteScrollInstance?.dataManager.removeEventListener()
187-
infiniteScrollInstance?.elementManager.flushAll()
188-
})
185+
onDestroy(() => infiniteScrollInstance?.flush())
189186
</script>
190187

191188
{#if !startElement && !reverse}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script lang="ts">
2+
import { inertia, InfiniteScroll } from '@inertiajs/svelte'
3+
import UserCard, { type User } from './UserCard.svelte'
4+
5+
export let users: { data: User[] }
6+
</script>
7+
8+
<div>
9+
<a href="/infinite-scroll" use:inertia>Go back to Links</a>
10+
<InfiniteScroll data="users" style="display: grid; gap: 20px">
11+
<div slot="loading" style="text-align: center; padding: 20px">Loading...</div>
12+
13+
{#each users.data as user (user.id)}
14+
<UserCard {user} />
15+
{/each}
16+
</InfiniteScroll>
17+
</div>

0 commit comments

Comments
 (0)