Skip to content

Commit

Permalink
fix(useInfiniteScroll)!: improve loading strategies, close #1701, close
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Apr 13, 2023
1 parent 7dd431b commit d3a2bca
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 27 deletions.
4 changes: 2 additions & 2 deletions packages/core/useInfiniteScroll/demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ref } from 'vue'
import { useInfiniteScroll } from '@vueuse/core'
const el = ref<HTMLElement | null>(null)
const data = ref([1, 2, 3, 4, 5, 6])
const data = ref([1])
useInfiniteScroll(
el,
Expand All @@ -17,7 +17,7 @@ useInfiniteScroll(

<template>
<div ref="el" class="flex flex-col gap-2 p-4 w-300px h-300px m-auto overflow-y-scroll bg-gray-500/5 rounded">
<div v-for="item in data" :key="item" class="h-30 bg-gray-500/5 rounded p-3">
<div v-for="item in data" :key="item" class="h-15 bg-gray-500/5 rounded p-3">
{{ item }}
</div>
</div>
Expand Down
66 changes: 41 additions & 25 deletions packages/core/useInfiniteScroll/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { computed, nextTick, reactive, ref, watch } from 'vue-demi'
import type { UnwrapNestedRefs } from 'vue-demi'
import { nextTick, reactive, watch } from 'vue-demi'
import type { MaybeRefOrGetter } from '@vueuse/shared'
import type { Awaitable, MaybeRefOrGetter } from '@vueuse/shared'
import { toValue } from '@vueuse/shared'
import type { UseScrollOptions } from '../useScroll'
import { useScroll } from '../useScroll'
Expand All @@ -21,11 +21,11 @@ export interface UseInfiniteScrollOptions extends UseScrollOptions {
direction?: 'top' | 'bottom' | 'left' | 'right'

/**
* Whether to preserve the current scroll position when loading more items.
* The interval time between two load more (to avoid too many invokes).
*
* @default false
* @default 100
*/
preserveScrollPosition?: boolean
interval?: number
}

/**
Expand All @@ -35,10 +35,14 @@ export interface UseInfiniteScrollOptions extends UseScrollOptions {
*/
export function useInfiniteScroll(
element: MaybeRefOrGetter<HTMLElement | SVGElement | Window | Document | null | undefined>,
onLoadMore: (state: UnwrapNestedRefs<ReturnType<typeof useScroll>>) => void | Promise<void>,
onLoadMore: (state: UnwrapNestedRefs<ReturnType<typeof useScroll>>) => Awaitable<void>,
options: UseInfiniteScrollOptions = {},
) {
const direction = options.direction ?? 'bottom'
const {
direction = 'bottom',
interval = 100,
} = options

const state = reactive(useScroll(
element,
{
Expand All @@ -50,27 +54,39 @@ export function useInfiniteScroll(
},
))

watch(
() => state.arrivedState[direction],
async (v) => {
if (v) {
const elem = toValue(element) as Element
const previous = {
height: elem?.scrollHeight ?? 0,
width: elem?.scrollWidth ?? 0,
}
const promise = ref<any>()
const isLoading = computed(() => !!promise.value)

await onLoadMore(state)
function checkAndLoad() {
const el = toValue(element) as HTMLElement
if (!el)
return

if (options.preserveScrollPosition && elem) {
nextTick(() => {
elem.scrollTo({
top: elem.scrollHeight - previous.height,
left: elem.scrollWidth - previous.width,
})
const isNarrower = (direction === 'bottom' || direction === 'top')
? el.scrollHeight <= el.clientHeight
: el.scrollWidth <= el.clientWidth

if (state.arrivedState[direction] || isNarrower) {
if (!promise.value) {
promise.value = Promise.all([
onLoadMore(state),
new Promise(resolve => setTimeout(resolve, interval)),
])
.finally(() => {
promise.value = null
nextTick(() => checkAndLoad())
})
}
}
},
}
}

watch(
() => [state.arrivedState[direction], toValue(element)],
checkAndLoad,
{ immediate: true },
)

return {
isLoading,
}
}

3 comments on commit d3a2bca

@floydback
Copy link

@floydback floydback commented on d3a2bca Apr 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit breaks my code.

  1. Now if I manually empty the content list, the 'useInfiniteScroll' event is not triggered.
  2. Sometimes when I scroll the list very fast (or have done multiple scrolls in less than 1 second), the onLoadMore method is stuck and executed an infinite number of times until I tap the scroll.

and yes, these problems can be detected only when using http request with a delay in the response

@martynchamberlin
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can confirm.

@antfu
Copy link
Member Author

@antfu antfu commented on d3a2bca Apr 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't comment on commits. Use #3019 instead. Thanks.

Please sign in to comment.