Skip to content

Commit 7ce0e69

Browse files
committed
fix: performance improvement for message list render item
1 parent 7f0fe8a commit 7ce0e69

File tree

17 files changed

+305
-300
lines changed

17 files changed

+305
-300
lines changed

package/src/components/Channel/Channel.tsx

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,12 @@ import {
102102
isImagePickerAvailable,
103103
NativeHandlers,
104104
} from '../../native';
105+
import {
106+
ChannelUnreadStateStore,
107+
ChannelUnreadStateStoreType,
108+
} from '../../state-store/channel-unread-state';
105109
import * as dbApi from '../../store/apis';
106-
import { ChannelUnreadState, FileTypes } from '../../types/types';
110+
import { FileTypes } from '../../types/types';
107111
import { addReactionToLocalState } from '../../utils/addReactionToLocalState';
108112
import { compressedImageURI } from '../../utils/compressImage';
109113
import { patchMessageTextCommand } from '../../utils/patchMessageTextCommand';
@@ -421,7 +425,7 @@ export type ChannelPropsWithContext = Pick<ChannelContextValue, 'channel'> &
421425
*/
422426
doMarkReadRequest?: (
423427
channel: ChannelType,
424-
setChannelUnreadUiState?: (state: ChannelUnreadState) => void,
428+
setChannelUnreadUiState?: (data: ChannelUnreadStateStoreType['channelUnreadState']) => void,
425429
) => void;
426430
/**
427431
* Overrides the Stream default send message request (Advanced usage only)
@@ -735,10 +739,13 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
735739
const [thread, setThread] = useState<LocalMessage | null>(threadProps || null);
736740
const [threadHasMore, setThreadHasMore] = useState(true);
737741
const [threadLoadingMore, setThreadLoadingMore] = useState(false);
738-
const [channelUnreadState, setChannelUnreadState] = useState<ChannelUnreadState | undefined>(
739-
undefined,
742+
const channelUnreadStateStore = useMemo(() => new ChannelUnreadStateStore(), []);
743+
const setChannelUnreadState = useCallback(
744+
(data: ChannelUnreadStateStoreType['channelUnreadState']) => {
745+
channelUnreadStateStore.channelUnreadState = data;
746+
},
747+
[channelUnreadStateStore],
740748
);
741-
742749
const { bottomSheetRef, closePicker, openPicker } = useAttachmentPickerBottomSheet();
743750

744751
const syncingChannelRef = useRef(false);
@@ -863,16 +870,14 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
863870
}
864871

865872
if (event.type === 'notification.mark_unread') {
866-
setChannelUnreadState((prev) => {
867-
if (!(event.last_read_at && event.user)) {
868-
return prev;
869-
}
870-
return {
871-
first_unread_message_id: event.first_unread_message_id,
872-
last_read: new Date(event.last_read_at),
873-
last_read_message_id: event.last_read_message_id,
874-
unread_messages: event.unread_messages ?? 0,
875-
};
873+
if (!(event.last_read_at && event.user)) {
874+
return;
875+
}
876+
setChannelUnreadState({
877+
first_unread_message_id: event.first_unread_message_id,
878+
last_read: new Date(event.last_read_at),
879+
last_read_message_id: event.last_read_message_id,
880+
unread_messages: event.unread_messages ?? 0,
876881
});
877882
}
878883

@@ -1707,7 +1712,8 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
17071712

17081713
const channelContext = useCreateChannelContext({
17091714
channel,
1710-
channelUnreadState,
1715+
channelUnreadState: channelUnreadStateStore.channelUnreadState,
1716+
channelUnreadStateStore,
17111717
disabled: !!channel?.data?.frozen,
17121718
EmptyStateIndicator,
17131719
enableMessageGroupingByUser,

package/src/components/Channel/hooks/useCreateChannelContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { ChannelContextValue } from '../../../contexts/channelContext/Chann
55
export const useCreateChannelContext = ({
66
channel,
77
channelUnreadState,
8+
channelUnreadStateStore,
89
disabled,
910
EmptyStateIndicator,
1011
enableMessageGroupingByUser,
@@ -44,12 +45,12 @@ export const useCreateChannelContext = ({
4445
const readUsers = Object.values(read);
4546
const readUsersLength = readUsers.length;
4647
const readUsersLastReads = readUsers.map(({ last_read }) => last_read.toISOString()).join();
47-
const stringifiedChannelUnreadState = JSON.stringify(channelUnreadState);
4848

4949
const channelContext: ChannelContextValue = useMemo(
5050
() => ({
5151
channel,
5252
channelUnreadState,
53+
channelUnreadStateStore,
5354
disabled,
5455
EmptyStateIndicator,
5556
enableMessageGroupingByUser,
@@ -94,7 +95,6 @@ export const useCreateChannelContext = ({
9495
membersLength,
9596
readUsersLength,
9697
readUsersLastReads,
97-
stringifiedChannelUnreadState,
9898
targetedMessage,
9999
threadList,
100100
watcherCount,

package/src/components/Message/Message.tsx

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,6 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
252252
handleRetry,
253253
handleThreadReply,
254254
isTargetedMessage,
255-
lastReceivedId,
256255
members,
257256
message,
258257
messageActions: messageActionsProp = defaultMessageActions,
@@ -650,7 +649,6 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
650649
isMessageAIGenerated,
651650
isMyMessage,
652651
lastGroupMessage: groupStyles?.[0] === 'single' || groupStyles?.[0] === 'bottom',
653-
lastReceivedId,
654652
members,
655653
message,
656654
messageContentOrder,
@@ -783,7 +781,6 @@ const areEqual = (prevProps: MessagePropsWithContext, nextProps: MessagePropsWit
783781
groupStyles: prevGroupStyles,
784782
isAttachmentEqual,
785783
isTargetedMessage: prevIsTargetedMessage,
786-
lastReceivedId: prevLastReceivedId,
787784
members: prevMembers,
788785
message: prevMessage,
789786
messagesContext: prevMessagesContext,
@@ -797,7 +794,6 @@ const areEqual = (prevProps: MessagePropsWithContext, nextProps: MessagePropsWit
797794
goToMessage: nextGoToMessage,
798795
groupStyles: nextGroupStyles,
799796
isTargetedMessage: nextIsTargetedMessage,
800-
lastReceivedId: nextLastReceivedId,
801797
members: nextMembers,
802798
message: nextMessage,
803799
messagesContext: nextMessagesContext,
@@ -826,17 +822,6 @@ const areEqual = (prevProps: MessagePropsWithContext, nextProps: MessagePropsWit
826822
return false;
827823
}
828824

829-
const lastReceivedIdChangedAndMatters =
830-
prevLastReceivedId !== nextLastReceivedId &&
831-
(prevLastReceivedId === prevMessage.id ||
832-
prevLastReceivedId === nextMessage.id ||
833-
nextLastReceivedId === prevMessage.id ||
834-
nextLastReceivedId === nextMessage.id);
835-
836-
if (lastReceivedIdChangedAndMatters) {
837-
return false;
838-
}
839-
840825
const goToMessageChangedAndMatters =
841826
nextMessage.quoted_message_id && prevGoToMessage !== nextGoToMessage;
842827

package/src/components/Message/MessageSimple/MessageContent.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,6 @@ export const MessageContent = (props: MessageContentProps) => {
535535
isEditedMessageOpen,
536536
isMessageAIGenerated,
537537
isMyMessage,
538-
lastReceivedId,
539538
message,
540539
messageContentOrder,
541540
onLongPress,
@@ -575,7 +574,6 @@ export const MessageContent = (props: MessageContentProps) => {
575574
isEditedMessageOpen,
576575
isMessageAIGenerated,
577576
isMyMessage,
578-
lastReceivedId,
579577
message,
580578
messageContentOrder,
581579
MessageError,
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import React from 'react';
2+
3+
import { View } from 'react-native';
4+
5+
import { LocalMessage } from 'stream-chat';
6+
7+
import { MessageListProps } from '../../../components/MessageList/MessageList';
8+
import { useChannelContext } from '../../../contexts/channelContext/ChannelContext';
9+
import { useChatContext } from '../../../contexts/chatContext/ChatContext';
10+
import { MessageContextValue } from '../../../contexts/messageContext/MessageContext';
11+
import { useMessagesContext } from '../../../contexts/messagesContext/MessagesContext';
12+
import { ThemeProvider, useTheme } from '../../../contexts/themeContext/ThemeContext';
13+
14+
import { Theme } from '../../../contexts/themeContext/utils/theme';
15+
import { useStateStore } from '../../../hooks/useStateStore';
16+
import { ChannelUnreadStateStoreType } from '../../../state-store/channel-unread-state';
17+
18+
const channelUnreadStateSelector = (state: ChannelUnreadStateStoreType) => ({
19+
first_unread_message_id: state.channelUnreadState?.first_unread_message_id,
20+
last_read: state.channelUnreadState?.last_read,
21+
last_read_message_id: state.channelUnreadState?.last_read_message_id,
22+
unread_messages: state.channelUnreadState?.unread_messages,
23+
});
24+
25+
export type MessageWrapperProps = Pick<MessageContextValue, 'goToMessage'> &
26+
Pick<MessageListProps, 'onThreadSelect'> & {
27+
isNewestMessage?: boolean;
28+
message: LocalMessage;
29+
modifiedTheme?: Theme;
30+
dateSeparatorDate?: Date;
31+
messageGroupStyles?: string[];
32+
};
33+
34+
export const MessageWrapper = (props: MessageWrapperProps) => {
35+
const {
36+
dateSeparatorDate,
37+
isNewestMessage,
38+
message,
39+
messageGroupStyles,
40+
goToMessage,
41+
onThreadSelect,
42+
modifiedTheme,
43+
} = props;
44+
const { client } = useChatContext();
45+
const { channelUnreadStateStore, channel, highlightedMessageId, threadList } =
46+
useChannelContext();
47+
const {
48+
InlineDateSeparator,
49+
InlineUnreadIndicator,
50+
Message,
51+
MessageSystem,
52+
myMessageTheme,
53+
shouldShowUnreadUnderlay,
54+
} = useMessagesContext();
55+
56+
const { first_unread_message_id, last_read, last_read_message_id, unread_messages } =
57+
useStateStore(channelUnreadStateStore.state, channelUnreadStateSelector);
58+
const {
59+
theme: {
60+
messageList: { messageContainer },
61+
screenPadding,
62+
},
63+
} = useTheme();
64+
if (!channel || channel.disconnected) {
65+
return null;
66+
}
67+
68+
const createdAtTimestamp = message.created_at && new Date(message.created_at).getTime();
69+
const lastReadTimestamp = last_read?.getTime();
70+
const isLastReadMessage =
71+
last_read_message_id === message.id ||
72+
(!unread_messages && createdAtTimestamp === lastReadTimestamp);
73+
74+
const showUnreadSeparator =
75+
isLastReadMessage &&
76+
!isNewestMessage &&
77+
// The `channelUnreadState?.first_unread_message_id` is here for sent messages unread label
78+
(!!first_unread_message_id || !!unread_messages);
79+
80+
const showUnreadUnderlay = !!shouldShowUnreadUnderlay && showUnreadSeparator;
81+
82+
const wrapMessageInTheme = client.userID === message.user?.id && !!myMessageTheme;
83+
const renderDateSeperator = dateSeparatorDate ? (
84+
<InlineDateSeparator date={dateSeparatorDate} />
85+
) : null;
86+
87+
const renderMessage = (
88+
<Message
89+
goToMessage={goToMessage}
90+
groupStyles={messageGroupStyles ?? []}
91+
isTargetedMessage={highlightedMessageId === message.id}
92+
message={message}
93+
onThreadSelect={onThreadSelect}
94+
showUnreadUnderlay={showUnreadUnderlay}
95+
style={[messageContainer]}
96+
threadList={threadList}
97+
/>
98+
);
99+
100+
return (
101+
<View testID={`message-list-item-${message.id}`}>
102+
{message.type === 'system' ? (
103+
<MessageSystem
104+
message={message}
105+
style={[{ paddingHorizontal: screenPadding }, messageContainer]}
106+
/>
107+
) : wrapMessageInTheme ? (
108+
<ThemeProvider mergedStyle={modifiedTheme}>
109+
<View testID={`message-list-item-${message.id}`}>
110+
{renderDateSeperator}
111+
{renderMessage}
112+
</View>
113+
</ThemeProvider>
114+
) : (
115+
<View testID={`message-list-item-${message.id}`}>
116+
{renderDateSeperator}
117+
{renderMessage}
118+
</View>
119+
)}
120+
{showUnreadUnderlay && <InlineUnreadIndicator />}
121+
</View>
122+
);
123+
};

package/src/components/Message/MessageSimple/__tests__/MessageStatus.test.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,7 @@ describe('MessageStatus', () => {
8181
<ChannelsStateProvider>
8282
<Chat client={chatClient} i18nInstance={i18nInstance}>
8383
<Channel channel={channel}>
84-
<MessageStatus
85-
lastReceivedId={staticMessage.id}
86-
message={staticMessage}
87-
readBy={readBy}
88-
/>
84+
<MessageStatus message={staticMessage} readBy={readBy} />
8985
</Channel>
9086
</Chat>
9187
</ChannelsStateProvider>,

package/src/components/Message/hooks/useCreateMessageContext.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ export const useCreateMessageContext = ({
2222
isMessageAIGenerated,
2323
isMyMessage,
2424
lastGroupMessage,
25-
lastReceivedId,
2625
members,
2726
message,
2827
messageContentOrder,
@@ -74,7 +73,6 @@ export const useCreateMessageContext = ({
7473
isMessageAIGenerated,
7574
isMyMessage,
7675
lastGroupMessage,
77-
lastReceivedId,
7876
members,
7977
message,
8078
messageContentOrder,
@@ -105,7 +103,6 @@ export const useCreateMessageContext = ({
105103
hasReactions,
106104
isEditedMessageOpen,
107105
lastGroupMessage,
108-
lastReceivedId,
109106
membersValue,
110107
myMessageThemeString,
111108
reactionsValue,

0 commit comments

Comments
 (0)