Skip to content

Commit d9b49bb

Browse files
authored
[UK-1013] Fix file view error and add debouncing (#57)
* Add debouncing logic to the MessageSearch component * Add optional chaining for isResendable of message * Add optional chaining for emojiCategories of message * Apply order to message search as 'ts'
1 parent 9258710 commit d9b49bb

File tree

10 files changed

+85
-24
lines changed

10 files changed

+85
-24
lines changed

src/smart-components/MessageSearch/dux/actionTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export const SET_QUERY_INVALID = 'SET_QUERY_INVALID';
66

77
export const SET_CURRENT_CHANNEL = 'SET_CURRENT_CHANNEL';
88
export const CHANNEL_INVALID = 'CHANNEL_INVALID';
9+
export const RESET_SEARCH_STRING = 'RESET_SEARCH_STRING';

src/smart-components/MessageSearch/dux/reducers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ export default function reducer(
7373
hasMoreResult: state.currentMessageSearchQuery.hasNext,
7474
};
7575
}
76+
case actionTypes.RESET_SEARCH_STRING: {
77+
return {
78+
...state,
79+
allMessages: [],
80+
};
81+
}
7682
default: {
7783
return state;
7884
}

src/smart-components/MessageSearch/hooks/useGetSearchedMessages.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import SendBird from 'sendbird';
66
interface MainProps {
77
currentChannel: SendbirdUIKit.GroupChannelType;
88
channelUrl: string;
9-
searchString?: string;
9+
requestString?: string;
1010
messageSearchQuery?: SendbirdUIKit.MessageSearchQueryType;
1111
onResultLoaded?: (
1212
messages?: Array<SendBird.UserMessage | SendBird.FileMessage | SendBird.AdminMessage>,
@@ -21,7 +21,7 @@ interface ToolProps {
2121
}
2222

2323
function useGetSearchedMessages(
24-
{ currentChannel, channelUrl, searchString, messageSearchQuery, onResultLoaded, retryCount }: MainProps,
24+
{ currentChannel, channelUrl, requestString, messageSearchQuery, onResultLoaded, retryCount }: MainProps,
2525
{ sdk, logger, messageSearchDispathcer }: ToolProps,
2626
): void {
2727
useEffect(() => {
@@ -30,13 +30,14 @@ function useGetSearchedMessages(
3030
payload: null,
3131
});
3232
if (sdk && channelUrl && sdk.createMessageSearchQuery && currentChannel) {
33-
if (searchString) {
33+
if (requestString) {
3434
const inputSearchMessageQueryObject = {
3535
...messageSearchQuery,
36+
order: 'ts' as const,
3637
channelUrl,
3738
messageTimestampFrom: currentChannel.invitedAt,
3839
};
39-
const createdQuery = sdk.createMessageSearchQuery(searchString, inputSearchMessageQueryObject);
40+
const createdQuery = sdk.createMessageSearchQuery(requestString, inputSearchMessageQueryObject);
4041
createdQuery.next((messages, error) => {
4142
if (!error) {
4243
logger.info('MessageSearch | useGetSearchedMessages: succeeded getting messages', messages);
@@ -69,7 +70,7 @@ function useGetSearchedMessages(
6970
logger.info('MessageSearch | useGetSeasrchedMessages: search string is empty');
7071
}
7172
}
72-
}, [channelUrl, messageSearchQuery, searchString, currentChannel, retryCount]);
73+
}, [channelUrl, messageSearchQuery, requestString, currentChannel, retryCount]);
7374
}
7475

7576
export default useGetSearchedMessages;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { useState, useEffect } from 'react';
2+
import * as messageActionTypes from '../dux/actionTypes';
3+
4+
interface DynamicParams {
5+
searchString: string;
6+
}
7+
8+
interface StaticParams {
9+
messageSearchDispathcer: ({ type: string, payload: any }) => void;
10+
}
11+
12+
const DEBOUNCING_TIME = 500;
13+
14+
function useSearchStringEffect(
15+
{ searchString }: DynamicParams,
16+
{ messageSearchDispathcer }: StaticParams,
17+
): string {
18+
const [requestString, setRequestString] = useState('');
19+
const [debouncingTimer, setDebouncingTimer] = useState(null);
20+
useEffect(() => {
21+
clearTimeout(debouncingTimer);
22+
if (searchString) {
23+
setDebouncingTimer(
24+
setTimeout(() => {
25+
setRequestString(searchString);
26+
}, DEBOUNCING_TIME)
27+
);
28+
} else {
29+
setRequestString('');
30+
messageSearchDispathcer({
31+
type: messageActionTypes.RESET_SEARCH_STRING,
32+
payload: '',
33+
});
34+
}
35+
}, [searchString]);
36+
return requestString;
37+
}
38+
39+
export default useSearchStringEffect;

src/smart-components/MessageSearch/index.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { LocalizationContext } from '../../lib/LocalizationContext';
1515
import MessageSearchFileItem from '../../ui/MessageSearchFileItem';
1616

1717
import SendbirdUIKit from '../../index';
18+
import useSearchStringEffect from './hooks/useSearchStringEffect';
1819

1920
const COMPONENT_CLASS_NAME = 'sendbird-message-search';
2021

@@ -80,12 +81,8 @@ function MessageSearch(props: Props): JSX.Element {
8081
};
8182

8283
// const
83-
const {
84-
sdkStore,
85-
} = stores;
86-
const {
87-
logger,
88-
} = config;
84+
const { sdkStore } = stores;
85+
const { logger } = config;
8986
const { sdk } = sdkStore;
9087
const sdkInit = sdkStore.initialized;
9188
const scrollRef = useRef(null);
@@ -112,8 +109,10 @@ function MessageSearch(props: Props): JSX.Element {
112109
{ sdk, logger, messageSearchDispathcer },
113110
);
114111

112+
const requestString = useSearchStringEffect({ searchString }, { messageSearchDispathcer });
113+
115114
useGetSearchMessages(
116-
{ currentChannel, channelUrl, searchString, messageSearchQuery, onResultLoaded, retryCount },
115+
{ currentChannel, channelUrl, requestString, messageSearchQuery, onResultLoaded, retryCount },
117116
{ sdk, logger, messageSearchDispathcer },
118117
);
119118

@@ -126,7 +125,7 @@ function MessageSearch(props: Props): JSX.Element {
126125
setRetryCount(retryCount + 1);
127126
};
128127

129-
if (isInvalid && searchString) {
128+
if (isInvalid && searchString && requestString) {
130129
return (
131130
<div className={COMPONENT_CLASS_NAME}>
132131
<PlaceHolder
@@ -137,7 +136,7 @@ function MessageSearch(props: Props): JSX.Element {
137136
);
138137
}
139138

140-
if (loading && searchString) {
139+
if (loading && searchString && requestString) {
141140
return (
142141
<div className={COMPONENT_CLASS_NAME}>
143142
<PlaceHolder type={PlaceHolderTypes.SEARCHING} />

src/ui/MessageContent/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export default function MessageContent({
153153
<MessageItemReactionMenu
154154
className="sendbird-message-content-menu__reaction-menu"
155155
message={message as UserMessage | FileMessage}
156+
channel={channel}
156157
userId={userId}
157158
spaceFromTrigger={{}}
158159
emojiContainer={emojiContainer}
@@ -233,6 +234,7 @@ export default function MessageContent({
233234
<MessageItemReactionMenu
234235
className="sendbird-message-content-menu__reaction-menu"
235236
message={message as UserMessage | FileMessage}
237+
channel={channel}
236238
userId={userId}
237239
spaceFromTrigger={{}}
238240
emojiContainer={emojiContainer}

src/ui/MessageItemMenu/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ export default function MessageItemMenu({
4444
const showMenuItemCopy: boolean = isUserMessage(message as UserMessage);
4545
const showMenuItemReply: boolean = false && !isFailedMessage(channel, message) && !isPendingMessage(channel, message);
4646
const showMenuItemEdit: boolean = (isUserMessage(message as UserMessage) && isSentMessage(channel, message) && isByMe);
47-
const showMenuItemResend: boolean = (isFailedMessage(channel, message) && message.isResendable() && isByMe);
48-
const showMenuItemDelete: boolean = (isSentMessage(channel, message) && isByMe);
47+
const showMenuItemResend: boolean = (isFailedMessage(channel, message) && message?.isResendable?.() && isByMe);
48+
const showMenuItemDelete: boolean = !isPendingMessage(channel, message) && isByMe;
4949

5050
if (!(showMenuItemCopy || showMenuItemEdit || showMenuItemResend || showMenuItemDelete)) {
5151
return null;

src/ui/MessageItemReactionMenu/__tests__/MessageItemReactionMenu.spec.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React from 'react';
2-
import {shallow} from 'enzyme';
32
import renderer from 'react-test-renderer';
43

54
import MessageItemReactionMenu from "../index";
@@ -8,7 +7,9 @@ describe('MessageItemReactionMenu', () => {
87
it('should do a snapshot test of the MessageItemReactionMenu DOM', function() {
98
const text = "example-text";
109
const component = renderer.create(
11-
<MessageItemReactionMenu />,
10+
<MessageItemReactionMenu
11+
message={{ sendingStatus: 'succeeded' }}
12+
/>,
1213
);
1314
let tree = component.toJSON();
1415
expect(tree).toMatchSnapshot();

src/ui/MessageItemReactionMenu/index.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import React, { ReactElement, useRef } from 'react';
2-
import { FileMessage, UserMessage, Emoji, Reaction, EmojiContainer } from 'sendbird';
2+
import { FileMessage, UserMessage, Emoji, Reaction, EmojiContainer, GroupChannel, OpenChannel } from 'sendbird';
33
import './index.scss';
44

55
import ContextMenu, { EmojiListItems } from '../ContextMenu';
66
import Icon, { IconTypes, IconColors } from '../Icon';
77
import IconButton from '../IconButton';
88
import ImageRenderer from '../ImageRenderer';
99
import ReactionButton from '../ReactionButton';
10-
import { getClassName, getEmojiListAll } from '../../utils';
10+
import { getClassName, getEmojiListAll, isPendingMessage, isFailedMessage } from '../../utils';
1111

1212
interface Props {
1313
className?: string | Array<string>;
1414
message: UserMessage | FileMessage;
15+
channel: GroupChannel | OpenChannel;
1516
userId: string;
1617
spaceFromTrigger?: Record<string, unknown>;
1718
emojiContainer?: EmojiContainer;
@@ -22,6 +23,7 @@ interface Props {
2223
export default function MessageItemReactionMenu({
2324
className,
2425
message,
26+
channel,
2527
userId,
2628
spaceFromTrigger = {},
2729
emojiContainer,
@@ -31,6 +33,10 @@ export default function MessageItemReactionMenu({
3133
const triggerRef = useRef(null);
3234
const containerRef = useRef(null);
3335

36+
if(isPendingMessage(channel, message) || isFailedMessage(channel, message)) {
37+
return null;
38+
}
39+
3440
return (
3541
<div
3642
className={getClassName([className, 'sendbird-message-item-reaction-menu'])}

src/utils/index.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export const getOutgoingMessageStates = (): OutgoingMessageStates => ({ ...Outgo
114114
export const getOutgoingMessageState = (channel: GroupChannel | OpenChannel, message: UserMessage | FileMessage): string => {
115115
if (message.sendingStatus === 'pending') return OutgoingMessageStates.PENDING;
116116
if (message.sendingStatus === 'failed') return OutgoingMessageStates.FAILED;
117-
if (channel.isGroupChannel()) {
117+
if (channel?.isGroupChannel?.()) {
118118
/* GroupChannel only */
119119
if ((channel as GroupChannel).getUnreadMemberCount(message) === 0) {
120120
return OutgoingMessageStates.READ;
@@ -137,6 +137,7 @@ export const isDeliveredMessage = (channel: GroupChannel, message: UserMessage |
137137
export const isReadMessage = (channel: GroupChannel, message: UserMessage | FileMessage): boolean => (
138138
getOutgoingMessageState(channel, message) === OutgoingMessageStates.READ
139139
);
140+
// TODO: Remove channel from the params, it seems unnecessary
140141
export const isFailedMessage = (channel: GroupChannel | OpenChannel, message: UserMessage | FileMessage): boolean => (
141142
getOutgoingMessageState(channel, message) === OutgoingMessageStates.FAILED
142143
);
@@ -288,13 +289,18 @@ export const copyToClipboard = (text: string): boolean => {
288289
};
289290

290291
export const getEmojiListAll = (emojiContainer: EmojiContainer): Array<Emoji> => (
291-
emojiContainer.emojiCategories
292-
.map((emojiCategory: EmojiCategory) => emojiCategory.emojis)
292+
emojiContainer?.emojiCategories?.map((emojiCategory: EmojiCategory) => emojiCategory.emojis)
293293
.reduce((prevArr: Array<Emoji>, currArr: Array<Emoji>) => prevArr.concat(currArr), [])
294294
);
295295
export const getEmojiMapAll = (emojiContainer: EmojiContainer): Map<string, Emoji> => {
296296
const emojiMap = new Map();
297-
emojiContainer.emojiCategories.forEach((category: EmojiCategory) => category.emojis.forEach((emoji: Emoji): void => { emojiMap.set(emoji.key, emoji) }));
297+
emojiContainer?.emojiCategories?.forEach((category: EmojiCategory) => {
298+
category?.emojis?.forEach((emoji: Emoji): void => {
299+
if (emoji && emoji.key) {
300+
emojiMap.set(emoji.key, emoji);
301+
}
302+
});
303+
});
298304
return emojiMap;
299305
};
300306

0 commit comments

Comments
 (0)