Skip to content

Commit a4c9567

Browse files
Sravan SHoonBaek
andauthored
Fix: Scroll towards top (#547)
Fix scroll in MessageList Debounce scroll events Co-authored-by: HoonBaek <[email protected]>
1 parent ee4c728 commit a4c9567

File tree

4 files changed

+70
-7
lines changed

4 files changed

+70
-7
lines changed

src/hooks/useDebounce.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
export function useDebounce<T extends(...args: any[]) => void>(callback: T, delay: number): T {
4+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
5+
6+
useEffect(() => {
7+
// Cleanup the timeout on unmount
8+
return () => {
9+
if (timeoutRef.current) {
10+
clearTimeout(timeoutRef.current);
11+
}
12+
};
13+
}, []);
14+
15+
function debounceFunction(...args: Parameters<T>) {
16+
if (timeoutRef.current) {
17+
clearTimeout(timeoutRef.current);
18+
}
19+
20+
timeoutRef.current = setTimeout(() => {
21+
callback(...args);
22+
}, delay);
23+
}
24+
25+
return debounceFunction as T;
26+
}

src/hooks/useHandleOnScrollCallback/__tests__/useHandleOnScrollCallback.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { renderHook } from '@testing-library/react';
22
import { useHandleOnScrollCallback, calcScrollBottom } from '../index';
33

4+
jest.useFakeTimers();
5+
6+
const SAFE_DELAY = 1000;
7+
48
const prepareMockParams = ({
59
scrollTop = 0,
610
scrollHeight = 0,
@@ -39,6 +43,8 @@ describe('useHandleOnScrollCallback', () => {
3943
const handleOnScroll = result.current;
4044
handleOnScroll();
4145

46+
jest.advanceTimersByTime(SAFE_DELAY);
47+
4248
// assert
4349
expect(params.setShowScrollDownButton).toHaveBeenCalledWith(true);
4450
});
@@ -52,6 +58,8 @@ describe('useHandleOnScrollCallback', () => {
5258
const handleOnScroll = result.current;
5359
handleOnScroll();
5460

61+
jest.advanceTimersByTime(SAFE_DELAY);
62+
5563
// assert
5664
expect(params.setShowScrollDownButton).toHaveBeenCalledWith(false);
5765
expect(params.onScroll).not.toHaveBeenCalled();
@@ -67,6 +75,8 @@ describe('useHandleOnScrollCallback', () => {
6775
const handleOnScroll = result.current;
6876
handleOnScroll();
6977

78+
jest.advanceTimersByTime(SAFE_DELAY);
79+
7080
// assert
7181
expect(params.onScroll).not.toHaveBeenCalled();
7282
});
@@ -103,6 +113,8 @@ describe('useHandleOnScrollCallback', () => {
103113
const handleOnScroll = result.current;
104114
handleOnScroll();
105115

116+
jest.advanceTimersByTime(SAFE_DELAY);
117+
106118
// assert
107119
expect(params.onScroll).toHaveBeenCalled();
108120
expect(element.scrollTop).toEqual(newScrollHeight);

src/hooks/useHandleOnScrollCallback/index.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import React, { useCallback } from 'react';
22
import { SCROLL_BUFFER } from '../../utils/consts';
3+
import { useDebounce } from '../useDebounce';
4+
5+
const DELAY = 500;
36

47
export interface UseHandleOnScrollCallbackProps {
58
hasMore: boolean;
6-
onScroll(fn: () => void): void;
9+
hasNext?: boolean;
10+
onScroll(callback: () => void): void;
711
scrollRef: React.RefObject<HTMLDivElement>;
812
setShowScrollDownButton?: React.Dispatch<React.SetStateAction<boolean>>;
913
}
@@ -14,11 +18,12 @@ export function calcScrollBottom(scrollHeight: number, scrollTop: number): numbe
1418

1519
export function useHandleOnScrollCallback({
1620
hasMore,
21+
hasNext,
1722
onScroll,
1823
scrollRef,
1924
setShowScrollDownButton,
2025
}: UseHandleOnScrollCallbackProps): () => void {
21-
return useCallback(() => {
26+
const scrollCb = useCallback(() => {
2227
const element = scrollRef?.current;
2328
if (element == null) {
2429
return;
@@ -38,19 +43,24 @@ export function useHandleOnScrollCallback({
3843
if (typeof setShowScrollDownButton === 'function') {
3944
setShowScrollDownButton(scrollHeight > scrollTop + clientHeight + 1);
4045
}
41-
if (!hasMore) {
42-
return;
43-
}
44-
if (scrollTop < SCROLL_BUFFER) {
46+
if (hasMore && scrollTop < SCROLL_BUFFER) {
4547
onScroll(() => {
4648
// sets the scroll position to the bottom of the new messages
4749
element.scrollTop = element.scrollHeight - scrollBottom;
4850
});
4951
}
52+
if (hasNext) {
53+
onScroll(() => {
54+
// sets the scroll position to the top of the new messages
55+
element.scrollTop = scrollTop - (scrollHeight - element.scrollHeight);
56+
});
57+
}
5058
}, [
5159
setShowScrollDownButton,
5260
hasMore,
5361
onScroll,
5462
scrollRef,
5563
]);
64+
65+
return useDebounce(scrollCb, DELAY);
5666
}

src/modules/Channel/components/MessageList/index.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ const MessageList: React.FC<MessageListProps> = ({
3636
const {
3737
allMessages,
3838
hasMorePrev,
39+
hasMoreNext,
3940
setInitialTimeStamp,
4041
setAnimatedMessageId,
4142
setHighLightedMessageId,
4243
isMessageGroupingEnabled,
4344
scrollRef,
45+
onScrollCallback,
4446
onScrollDownCallback,
4547
messagesDispatcher,
4648
messageActionTypes,
@@ -70,7 +72,19 @@ const MessageList: React.FC<MessageListProps> = ({
7072
scrollHeight,
7173
} = element;
7274

73-
if (isAboutSame(clientHeight + scrollTop, scrollHeight, SCROLL_BUFFER)) {
75+
if (isAboutSame(scrollTop, 0, SCROLL_BUFFER)) {
76+
onScrollCallback((messages) => {
77+
if (messages) {
78+
try {
79+
//
80+
} catch (error) {
81+
//
82+
}
83+
}
84+
});
85+
}
86+
87+
if (isAboutSame(clientHeight + scrollTop, scrollHeight, SCROLL_BUFFER) && hasMoreNext) {
7488
onScrollDownCallback(([messages]) => {
7589
if (messages) {
7690
try {
@@ -116,6 +130,7 @@ const MessageList: React.FC<MessageListProps> = ({
116130

117131
const handleOnScroll = useHandleOnScrollCallback({
118132
hasMore: hasMorePrev,
133+
hasNext: hasMoreNext,
119134
onScroll,
120135
scrollRef,
121136
});

0 commit comments

Comments
 (0)