Skip to content

Commit 1020263

Browse files
committed
fix(InfiniteScroll): defer initial scroll check to prevent premature pagination on mount
1 parent 1a81668 commit 1020263

4 files changed

Lines changed: 29 additions & 45 deletions

File tree

src/components/InfiniteScrollPaginator/InfiniteScroll.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,18 @@ export const InfiniteScroll = (props: PropsWithChildren<InfiniteScrollProps>) =>
109109

110110
scrollElement.addEventListener('scroll', scrollListener, useCapture);
111111
scrollElement.addEventListener('resize', scrollListener, useCapture);
112-
scrollListener();
112+
113+
// Defer the initial proximity check so that any pending scroll-to-bottom
114+
// from useLayoutEffect (e.g. the MessageList settle pass) has been applied
115+
// to the DOM before we evaluate whether more pages should be loaded.
116+
// Without this, scrollTop is still 0 on mount which falsely triggers
117+
// loadPreviousPage and breaks the initial scroll position.
118+
const rafId = requestAnimationFrame(() => {
119+
scrollListener();
120+
});
113121

114122
return () => {
123+
cancelAnimationFrame(rafId);
115124
scrollElement.removeEventListener('scroll', scrollListener, useCapture);
116125
scrollElement.removeEventListener('resize', scrollListener, useCapture);
117126
};

src/components/InfiniteScrollPaginator/__tests__/InfiniteScroll.test.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,25 @@ describe('InfiniteScroll', () => {
9090
},
9191
);
9292

93+
it('should not call loadPreviousPage synchronously on mount', () => {
94+
renderComponent({
95+
hasPreviousPage: true,
96+
});
97+
98+
expect(loadPreviousPage).not.toHaveBeenCalled();
99+
});
100+
101+
it('should defer the initial scroll check to a requestAnimationFrame', () => {
102+
const rafSpy = vi.spyOn(window, 'requestAnimationFrame');
103+
104+
renderComponent({
105+
hasPreviousPage: true,
106+
});
107+
108+
expect(rafSpy).toHaveBeenCalledWith(expect.any(Function));
109+
rafSpy.mockRestore();
110+
});
111+
93112
describe('Rendering loader', () => {
94113
const getRenderResult = () => {
95114
const props = fromPartial<InfiniteScrollProps>({

src/components/MessageList/hooks/MessageList/__tests__/useScrollLocationLogic.test.tsx

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/components/MessageList/hooks/MessageList/useScrollLocationLogic.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -399,15 +399,6 @@ export const useScrollLocationLogic = (params: UseScrollLocationLogicParams) =>
399399
showNewMessages: () => setHasNewMessages(true),
400400
});
401401

402-
useLayoutEffect(() => {
403-
if (!listElement) return;
404-
405-
const initialScrollTop = listElement.scrollTop;
406-
previousScrollTopRef.current = initialScrollTop;
407-
// Keep pagination mode selection in sync with the actual initial DOM position.
408-
updateScrollTop(initialScrollTop, null);
409-
}, [listElement, updateScrollTop]);
410-
411402
useLayoutEffect(() => {
412403
previousHasMoreNewerRef.current = hasMoreNewer;
413404
}, [hasMoreNewer]);

0 commit comments

Comments
 (0)