Skip to content

Commit c334821

Browse files
authored
feat: apply useHandleOnScrollCallback to GroupChannel MessageList (#492)
### Description Of Changes Resolves [UIKIT-3620](https://sendbird.atlassian.net/browse/UIKIT-3620) I applied useHandleOnScrollCallback hook that @HoonBaek made in #460 for OpenChannel to GroupChannel too.
1 parent 79a3ed1 commit c334821

File tree

6 files changed

+57
-79
lines changed

6 files changed

+57
-79
lines changed

src/smart-components/OpenChannel/components/OpenChannelMessageList/__tests__/useHandleOnScrollCallback.spec.ts renamed to src/hooks/useHandleOnScrollCallback/__tests__/useHandleOnScrollCallback.spec.ts

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,21 @@
1-
import { useHandleOnScrollCallback, calcScrollBottom } from '../useHandleOnScrollCallback';
21
import { renderHook } from '@testing-library/react';
2+
import { useHandleOnScrollCallback, calcScrollBottom } from '../index';
33

4-
const prepareMockScrollEvent = ({
4+
const prepareMockParams = ({
55
scrollTop = 0,
66
scrollHeight = 0,
77
clientHeight = 0,
88
}) => {
9-
return {
10-
target: {
11-
scrollTop,
12-
scrollHeight,
13-
clientHeight,
14-
},
15-
} as unknown as React.UIEvent<HTMLDivElement, UIEvent>;
16-
};
17-
18-
const prepareMockParams = () => {
199
const onScroll = jest.fn((cb) => {
2010
cb();
2111
});
2212
const setShowScrollDownButton = jest.fn();
2313
const hasMore = true;
2414
const scrollRef = {
2515
current: {
26-
scrollTop: 0,
27-
scrollHeight: 0,
28-
clientHeight: 0,
16+
scrollTop,
17+
scrollHeight,
18+
clientHeight,
2919
},
3020
} as React.RefObject<HTMLDivElement>;
3121
return {
@@ -39,28 +29,28 @@ const prepareMockParams = () => {
3929
describe('useHandleOnScrollCallback', () => {
4030
it('should call setShowScrollDownButton with true when scrollHeight is larger', () => {
4131
// prepare
42-
const params = prepareMockParams();
43-
const event = prepareMockScrollEvent({ scrollHeight: 100, scrollTop: 5 });
32+
const params = prepareMockParams({
33+
scrollHeight: 100, scrollTop: 5,
34+
});
4435
params.hasMore = false;
4536

4637
// call
4738
const { result } = renderHook(() => useHandleOnScrollCallback(params));
4839
const handleOnScroll = result.current;
49-
handleOnScroll(event);
40+
handleOnScroll();
5041

5142
// assert
5243
expect(params.setShowScrollDownButton).toHaveBeenCalledWith(true);
5344
});
5445

5546
it('should call setShowScrollDownButton with false when scrollHeight is shorter', () => {
5647
// prepare
57-
const params = prepareMockParams();
58-
const event = prepareMockScrollEvent({ scrollHeight: 5, scrollTop: 100 });
48+
const params = prepareMockParams({ scrollHeight: 5, scrollTop: 100 });
5949

6050
// call
6151
const { result } = renderHook(() => useHandleOnScrollCallback(params));
6252
const handleOnScroll = result.current;
63-
handleOnScroll(event);
53+
handleOnScroll();
6454

6555
// assert
6656
expect(params.setShowScrollDownButton).toHaveBeenCalledWith(false);
@@ -69,54 +59,52 @@ describe('useHandleOnScrollCallback', () => {
6959

7060
it('should not call onScroll if hasMore is false', () => {
7161
// prepare
72-
const params = prepareMockParams();
62+
const params = prepareMockParams({});
7363
params.hasMore = false;
74-
const event = prepareMockScrollEvent({});
7564

7665
// call
7766
const { result } = renderHook(() => useHandleOnScrollCallback(params));
7867
const handleOnScroll = result.current;
79-
handleOnScroll(event);
68+
handleOnScroll();
8069

8170
// assert
8271
expect(params.onScroll).not.toHaveBeenCalled();
8372
});
8473

8574
it('should not execute onScroll if scrollTop is greater than SCROLL_BUFFER', () => {
8675
// prepare
87-
const params = prepareMockParams();
88-
const event = prepareMockScrollEvent({ scrollTop: 100 });
76+
const params = prepareMockParams({ scrollTop: 100 });
8977

9078
// call
9179
const { result } = renderHook(() => useHandleOnScrollCallback(params));
9280
const handleOnScroll = result.current;
93-
handleOnScroll(event);
81+
handleOnScroll();
9482

9583
// assert
9684
expect(params.onScroll).not.toHaveBeenCalled();
9785
});
9886

9987
it('should execute if scrollTop is less than SCROLL_BUFFER', () => {
10088
// prepare
101-
const params = prepareMockParams();
102-
// @ts-ignore
103-
params.scrollRef.current.scrollHeight = 4459;
104-
const event = prepareMockScrollEvent({
89+
const params = prepareMockParams({
10590
clientHeight: 723,
106-
scrollHeight: 1174,
91+
scrollHeight: 4459,
10792
scrollTop: 0,
10893
});
94+
const element = params.scrollRef.current;
95+
// @ts-ignore
96+
element.scrollHeight = 4459;
10997
// @ts-ignore
110-
const scrollBottom = calcScrollBottom(event.target.scrollHeight, event.target.scrollTop);
111-
const newScrollHeight = params.scrollRef.current.scrollHeight - scrollBottom;
98+
const scrollBottom = calcScrollBottom(element.scrollHeight, element.scrollTop);
99+
const newScrollHeight = element.scrollHeight - scrollBottom;
112100

113101
// call
114102
const { result } = renderHook(() => useHandleOnScrollCallback(params));
115103
const handleOnScroll = result.current;
116-
handleOnScroll(event);
104+
handleOnScroll();
117105

118106
// assert
119107
expect(params.onScroll).toHaveBeenCalled();
120-
expect(params.scrollRef.current.scrollTop).toEqual(newScrollHeight);
108+
expect(element.scrollTop).toEqual(newScrollHeight);
121109
});
122110
});

src/smart-components/OpenChannel/components/OpenChannelMessageList/useHandleOnScrollCallback.ts renamed to src/hooks/useHandleOnScrollCallback/index.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
import React, { useCallback } from 'react';
2-
import { SCROLL_BUFFER } from '../../../../utils/consts';
2+
import { SCROLL_BUFFER } from '../../utils/consts';
33

44
export interface UseHandleOnScrollCallbackProps {
5-
setShowScrollDownButton: React.Dispatch<React.SetStateAction<boolean>>;
65
hasMore: boolean;
76
onScroll(fn: () => void): void;
87
scrollRef: React.RefObject<HTMLDivElement>;
8+
setShowScrollDownButton?: React.Dispatch<React.SetStateAction<boolean>>;
99
}
1010

1111
export function calcScrollBottom(scrollHeight: number, scrollTop: number): number {
1212
return scrollHeight - scrollTop
1313
}
1414

1515
export function useHandleOnScrollCallback({
16-
setShowScrollDownButton,
1716
hasMore,
1817
onScroll,
1918
scrollRef,
20-
}: UseHandleOnScrollCallbackProps): (e: React.UIEvent<HTMLElement>) => void {
21-
return useCallback((e) => {
22-
const element = e.target as Element;
19+
setShowScrollDownButton,
20+
}: UseHandleOnScrollCallbackProps): () => void {
21+
return useCallback(() => {
22+
const element = scrollRef.current;
2323
const {
2424
scrollTop,
2525
scrollHeight,
@@ -31,10 +31,8 @@ export function useHandleOnScrollCallback({
3131
const scrollBottom = calcScrollBottom(scrollHeight, scrollTop);
3232
// even if there is more to fetch or not,
3333
// we still have to show the scroll to bottom button
34-
if (scrollHeight > scrollTop + clientHeight + 1) {
35-
setShowScrollDownButton(true);
36-
} else {
37-
setShowScrollDownButton(false);
34+
if (typeof setShowScrollDownButton === 'function') {
35+
setShowScrollDownButton(scrollHeight > scrollTop + clientHeight + 1)
3836
}
3937
if (!hasMore) {
4038
return;

src/smart-components/Channel/components/Message/index.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import RemoveMessageModal from '../RemoveMessageModal';
2525
import { MessageInputKeys } from '../../../../ui/MessageInput/const';
2626
import { EveryMessage, RenderCustomSeparatorProps, RenderMessageProps } from '../../../../types';
2727
import { useLocalization } from '../../../../lib/LocalizationContext';
28+
import { useHandleOnScrollCallback } from '../../../../hooks/useHandleOnScrollCallback'
2829

2930
type MessageUIProps = {
3031
message: EveryMessage;
@@ -84,6 +85,7 @@ const Message = ({
8485
onQuoteMessageClick,
8586
onMessageAnimated,
8687
onMessageHighlighted,
88+
onScrollCallback,
8789
} = useChannelContext();
8890
const [showEdit, setShowEdit] = useState(false);
8991
const [showRemove, setShowRemove] = useState(false);
@@ -109,6 +111,12 @@ const Message = ({
109111
|| isDisabledBecauseMuted(currentGroupChannel)
110112
|| !isOnline;
111113

114+
const handleOnScroll = useHandleOnScrollCallback({
115+
hasMore: false,
116+
onScroll: onScrollCallback,
117+
scrollRef: messageScrollRef,
118+
});
119+
112120
useEffect(() => {
113121
if (mentionedUsers?.length >= maxUserMentionCount) {
114122
setAbleMention(false);
@@ -139,7 +147,7 @@ const Message = ({
139147
let animationTimeout = null;
140148
let messageHighlightedTimeout = null;
141149
if (highLightedMessageId === message.messageId && messageScrollRef?.current) {
142-
messageScrollRef.current.scrollIntoView({ block: 'center', inline: 'center' });
150+
handleOnScroll();
143151
setIsAnimated(false);
144152
animationTimeout = setTimeout(() => {
145153
setIsHighlighted(true);
@@ -161,7 +169,7 @@ const Message = ({
161169
let animationTimeout = null;
162170
let messageAnimatedTimeout = null;
163171
if (animatedMessageId === message.messageId && messageScrollRef?.current) {
164-
messageScrollRef.current.scrollIntoView({ block: 'center', inline: 'center' });
172+
handleOnScroll();
165173
setIsHighlighted(false);
166174
animationTimeout = setTimeout(() => {
167175
setIsAnimated(true);

src/smart-components/Channel/components/MessageList/index.tsx

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { EveryMessage } from '../../../..';
1616
import useSendbirdStateContext from '../../../../hooks/useSendbirdStateContext';
1717
import { UserMessage } from '@sendbird/chat/message';
1818
import { MessageProvider } from '../../../Message/context/MessageProvider';
19+
import { useHandleOnScrollCallback } from '../../../../hooks/useHandleOnScrollCallback'
1920

2021
export interface MessageListProps {
2122
className?: string;
@@ -25,8 +26,6 @@ export interface MessageListProps {
2526
renderPlaceholderLoader?: () => React.ReactElement;
2627
}
2728

28-
const SCROLL_REF_CLASS_NAME = '.sendbird-msg--scroll-ref';
29-
3029
const MessageList: React.FC<MessageListProps> = ({
3130
className = '',
3231
renderMessage,
@@ -42,7 +41,6 @@ const MessageList: React.FC<MessageListProps> = ({
4241
setHighLightedMessageId,
4342
isMessageGroupingEnabled,
4443
scrollRef,
45-
onScrollCallback,
4644
onScrollDownCallback,
4745
messagesDispatcher,
4846
messageActionTypes,
@@ -68,25 +66,6 @@ const MessageList: React.FC<MessageListProps> = ({
6866
scrollHeight,
6967
} = element;
7068

71-
if (scrollTop === 0) {
72-
if (!hasMorePrev) {
73-
return;
74-
}
75-
const nodes = scrollRef.current.querySelectorAll(SCROLL_REF_CLASS_NAME);
76-
const first = nodes && nodes[0];
77-
onScrollCallback(([messages]) => {
78-
if (messages) {
79-
// https://github.com/scabbiaza/react-scroll-position-on-updating-dom
80-
// Set block to nearest to prevent unexpected scrolling from outer components
81-
try {
82-
first.scrollIntoView({ block: 'start', inline: 'nearest' });
83-
} catch (error) {
84-
//
85-
}
86-
}
87-
});
88-
}
89-
9069
if (isAboutSame(clientHeight + scrollTop, scrollHeight, SCROLL_BUFFER)) {
9170
onScrollDownCallback(([messages]) => {
9271
if (messages) {
@@ -134,6 +113,12 @@ const MessageList: React.FC<MessageListProps> = ({
134113
}
135114
};
136115

116+
const handleOnScroll = useHandleOnScrollCallback({
117+
hasMore: hasMorePrev,
118+
onScroll,
119+
scrollRef,
120+
});
121+
137122
if (loading) {
138123
return (typeof renderPlaceholderLoader === 'function')
139124
? renderPlaceholderLoader()
@@ -152,7 +137,7 @@ const MessageList: React.FC<MessageListProps> = ({
152137
<div
153138
className="sendbird-conversation__messages-padding"
154139
ref={scrollRef}
155-
onScroll={onScroll}
140+
onScroll={handleOnScroll}
156141
>
157142
{allMessagesFiltered.map((m, idx) => {
158143
const {
@@ -211,7 +196,7 @@ const MessageList: React.FC<MessageListProps> = ({
211196
/>
212197
{
213198
// This flag is an unmatched variable
214-
(scrollBottom > 1) && (
199+
scrollBottom > 1 && (
215200
<div
216201
className="sendbird-conversation__scroll-bottom-button"
217202
onClick={onClickScrollBot}

src/smart-components/Channel/components/SuggestedMentionList/SuggestedUserMentionItem.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,11 @@ function SuggestedUserMentionItem(props: SuggestedUserMentionItemProps): JSX.Ele
3131
const scrollRef = useRef(null);
3232
const { stringSet = {} } = useContext(LocalizationContext);
3333
useEffect(() => {
34-
if (isFocused) {
35-
if (parentScrollRef?.current?.scrollTop >= scrollRef?.current?.offsetTop) {
36-
scrollRef?.current?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
37-
} else if (parentScrollRef?.current?.scrollTop + parentScrollRef?.current?.clientHeight <= scrollRef?.current?.offsetTop) {
38-
scrollRef?.current?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
39-
}
34+
if (isFocused
35+
&& (parentScrollRef?.current?.scrollTop >= scrollRef?.current?.offsetTop
36+
|| parentScrollRef?.current?.scrollTop + parentScrollRef?.current?.clientHeight <= scrollRef?.current?.offsetTop
37+
)) {
38+
scrollRef?.current?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
4039
}
4140
}, [isFocused]);
4241
const customMentionItem = useMemo(() => {

src/smart-components/OpenChannel/components/OpenChannelMessageList/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import { compareMessagesForGrouping } from '../../context/utils';
1111
import { useOpenChannelContext } from '../../context/OpenChannelProvider';
1212
import OpenChannelMessage from '../OpenChannelMessage';
1313
import { RenderMessageProps } from '../../../../types';
14-
import { useHandleOnScrollCallback } from './useHandleOnScrollCallback';
1514
import { MessageProvider } from '../../../Message/context/MessageProvider';
1615
import useSendbirdStateContext from '../../../../hooks/useSendbirdStateContext';
16+
import { useHandleOnScrollCallback } from '../../../../hooks/useHandleOnScrollCallback';
1717

1818
export type OpenchannelMessageListProps = {
1919
renderMessage?: (props: RenderMessageProps) => React.ElementType<RenderMessageProps>;

0 commit comments

Comments
 (0)