Skip to content

Commit c446c9e

Browse files
committed
fix(useInfiniteScroll): narrow scrollMethod trigger scope to post-fetch only, add regression tests
1 parent c559d7e commit c446c9e

2 files changed

Lines changed: 89 additions & 0 deletions

File tree

packages/hooks/src/useInfiniteScroll/__tests__/index.spec.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,4 +532,84 @@ describe('useInfiniteScroll', () => {
532532
scrollHeightSpy.mockRestore();
533533
clientHeightSpy.mockRestore();
534534
});
535+
536+
test('should auto loadMore after initial load when container is not full, and second request receives the latest finalData', async () => {
537+
setTargetInfo('scrollTop', 0);
538+
const scrollHeightSpy = vi.spyOn(targetEl, 'scrollHeight', 'get').mockImplementation(() => 50);
539+
const clientHeightSpy = vi.spyOn(targetEl, 'clientHeight', 'get').mockImplementation(() => 300);
540+
541+
let capturedLastData: any;
542+
const service = vi.fn(async (lastData?: any) => {
543+
capturedLastData = lastData;
544+
await sleep(1000);
545+
if (!lastData) {
546+
return { list: [1, 2, 3], nextId: 1 };
547+
}
548+
return { list: [4, 5, 6] };
549+
});
550+
551+
const { result } = setup(service, {
552+
target: targetEl,
553+
isNoMore: (d) => d?.nextId === undefined,
554+
});
555+
556+
// Wait for initial load to complete
557+
await act(async () => {
558+
vi.advanceTimersByTime(1000);
559+
});
560+
561+
// Container is not full, so scrollMethod should have auto-triggered loadMore
562+
expect(result.current.loadingMore).toBe(true);
563+
// The second call must receive the up-to-date finalData (not the stale closure value)
564+
expect(capturedLastData).toMatchObject({ list: [1, 2, 3], nextId: 1 });
565+
566+
await act(async () => {
567+
vi.advanceTimersByTime(1000);
568+
});
569+
expect(result.current.data?.list).toMatchObject([1, 2, 3, 4, 5, 6]);
570+
expect(result.current.noMore).toBe(true);
571+
572+
scrollHeightSpy.mockRestore();
573+
clientHeightSpy.mockRestore();
574+
});
575+
576+
test('mutate should not trigger extra loadMore', async () => {
577+
setTargetInfo('scrollTop', 0);
578+
const scrollHeightSpy = vi.spyOn(targetEl, 'scrollHeight', 'get').mockImplementation(() => 50);
579+
const clientHeightSpy = vi.spyOn(targetEl, 'clientHeight', 'get').mockImplementation(() => 300);
580+
581+
const service = vi.fn(mockRequest);
582+
const { result } = setup(service, {
583+
target: targetEl,
584+
isNoMore: (d) => d?.nextId === undefined,
585+
});
586+
587+
// Wait for initial load to complete; this auto-triggers a second load
588+
await act(async () => {
589+
vi.advanceTimersByTime(1000);
590+
});
591+
// Wait for the auto-triggered second load to complete
592+
await act(async () => {
593+
vi.advanceTimersByTime(1000);
594+
});
595+
expect(result.current.noMore).toBe(true);
596+
597+
const callCountAfterLoad = service.mock.calls.length;
598+
599+
// Mutate with data that makes noMore false, so loadMore *would* fire if scrollMethod ran
600+
act(() => {
601+
result.current.mutate({ list: [10, 20], nextId: 1 });
602+
});
603+
604+
// Wait for any effects to run
605+
await act(async () => {
606+
vi.advanceTimersByTime(100);
607+
});
608+
609+
// mutate should NOT trigger additional requests via scrollMethod
610+
expect(service).toBeCalledTimes(callCountAfterLoad);
611+
612+
scrollHeightSpy.mockRestore();
613+
clientHeightSpy.mockRestore();
614+
});
535615
});

packages/hooks/src/useInfiniteScroll/index.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ const useInfiniteScroll = <TData extends Data>(
3131
const lastScrollTop = useRef<number>(undefined);
3232
// scrollBottom is used to record the distance from the bottom of the scroll bar
3333
const scrollBottom = useRef<number>(0);
34+
// flag: set in onSuccess (bottom direction only) to trigger scrollMethod via effect
35+
const shouldScrollCheckRef = useRef(false);
3436

3537
const noMore = useMemo(() => {
3638
if (!isNoMore) {
@@ -80,6 +82,9 @@ const useInfiniteScroll = <TData extends Data>(
8082
});
8183
});
8284

85+
if (!isScrollToTop) {
86+
shouldScrollCheckRef.current = true;
87+
}
8388
onSuccess?.(d.currentData);
8489
},
8590
onError: (e) => onError?.(e),
@@ -143,6 +148,10 @@ const useInfiniteScroll = <TData extends Data>(
143148
}
144149
};
145150
useUpdateEffect(() => {
151+
if (!shouldScrollCheckRef.current) {
152+
return;
153+
}
154+
shouldScrollCheckRef.current = false;
146155
scrollMethod();
147156
}, [finalData]);
148157

0 commit comments

Comments
 (0)