@@ -4,14 +4,17 @@ import type { StreamMessage } from '../../../../context/ChannelStateContext';
44
55import type { DefaultStreamChatGenerics } from '../../../../types/types' ;
66
7- const STATUSES_EXCLUDED_FROM_PREPEND = [ 'sending' , 'failed' ] ;
7+ const STATUSES_EXCLUDED_FROM_PREPEND = ( {
8+ failed : true ,
9+ sending : true ,
10+ } as const ) as Record < string , boolean > ;
811
912export function usePrependedMessagesCount <
1013 StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
1114> ( messages : StreamMessage < StreamChatGenerics > [ ] , hasDateSeparator : boolean ) {
1215 const firstRealMessageIndex = hasDateSeparator ? 1 : 0 ;
13- const firstMessageId = useRef < string > ( ) ;
14- const earliestMessageId = useRef < string > ( ) ;
16+ const firstMessageOnFirstLoadedPage = useRef < StreamMessage < StreamChatGenerics > > ( ) ;
17+ const previousFirstMessageOnFirstLoadedPage = useRef < StreamMessage < StreamChatGenerics > > ( ) ;
1518 const previousNumItemsPrepended = useRef ( 0 ) ;
1619
1720 const numItemsPrepended = useMemo ( ( ) => {
@@ -20,51 +23,56 @@ export function usePrependedMessagesCount<
2023 return 0 ;
2124 }
2225
23- const currentFirstMessageId = messages ?. [ firstRealMessageIndex ] ?. id ;
24- // if no new messages were prepended, return early (same amount as before)
25- if ( currentFirstMessageId === earliestMessageId . current ) {
26+ const currentFirstMessage = messages ?. [ firstRealMessageIndex ] ;
27+
28+ const noNewMessages =
29+ currentFirstMessage ?. id === previousFirstMessageOnFirstLoadedPage . current ?. id ;
30+
31+ // This is possible only, when sending messages very quickly (basically single char messages submitted like a crazy) in empty channel (first page)
32+ // Optimistic UI update, when sending messages, can lead to a situation, when
33+ // the order of the messages changes for a moment. This can happen, when a user
34+ // sends multiple messages withing few milliseconds. E.g. we send a message A
35+ // then message B. At first we have message array with both messages of status "sending"
36+ // then response for message A is received with a new - later - created_at timestamp
37+ // this leads to rearrangement of 1.B ("sending"), 2.A ("received"). Still firstMessageOnFirstLoadedPage.current
38+ // points to message A, but now this message has index 1 => previousNumItemsPrepended.current === 1
39+ // That in turn leads to incorrect index calculation in VirtualizedMessageList trying to access a message
40+ // at non-existent index. Therefore, we ignore messages of status "sending" / "failed" in order they are
41+ // not considered as prepended messages.
42+ const firstMsgMovedAfterMessagesInExcludedStatus =
43+ currentFirstMessage ?. status && STATUSES_EXCLUDED_FROM_PREPEND [ currentFirstMessage . status ] ;
44+
45+ if ( noNewMessages || firstMsgMovedAfterMessagesInExcludedStatus ) {
2646 return previousNumItemsPrepended . current ;
2747 }
2848
29- if ( ! firstMessageId . current ) {
30- firstMessageId . current = currentFirstMessageId ;
49+ if ( ! firstMessageOnFirstLoadedPage . current ) {
50+ firstMessageOnFirstLoadedPage . current = currentFirstMessage ;
3151 }
32- earliestMessageId . current = currentFirstMessageId ;
52+ previousFirstMessageOnFirstLoadedPage . current = currentFirstMessage ;
3353 // if new messages were prepended, find out how many
3454 // start with this number because there cannot be fewer prepended items than before
35- let adjustPrependedMessageCount = 0 ;
36- for ( let i = previousNumItemsPrepended . current ; i < messages . length ; i += 1 ) {
37- // Optimistic UI update, when sending messages, can lead to a situation, when
38- // the order of the messages changes for a moment. This can happen, when a user
39- // sends multiple messages withing few milliseconds. E.g. we send a message A
40- // then message B. At first we have message array with both messages of status "sending"
41- // then response for message A is received with a new - later - created_at timestamp
42- // this leads to rearrangement of 1.B ("sending"), 2.A ("received"). Still firstMessageId.current
43- // points to message A, but now this message has index 1 => previousNumItemsPrepended.current === 1
44- // That in turn leads to incorrect index calculation in VirtualizedMessageList trying to access a message
45- // at non-existent index. Therefore, we ignore messages of status "sending" / "failed" in order they are
46- // not considered as prepended messages.
47- if (
48- messages [ i ] ?. status &&
49- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
50- STATUSES_EXCLUDED_FROM_PREPEND . includes ( messages [ i ] . status ! ) &&
51- messages [ i ] . id !== firstMessageId . current
52- ) {
53- adjustPrependedMessageCount ++ ;
54- }
55- if ( messages [ i ] . id === firstMessageId . current ) {
56- previousNumItemsPrepended . current = i - adjustPrependedMessageCount ;
57- return previousNumItemsPrepended . current ;
55+ for (
56+ let prependedMessageCount = previousNumItemsPrepended . current ;
57+ prependedMessageCount < messages . length ;
58+ prependedMessageCount += 1
59+ ) {
60+ const messageIsFirstOnFirstLoadedPage =
61+ messages [ prependedMessageCount ] . id === firstMessageOnFirstLoadedPage . current ?. id ;
62+
63+ if ( messageIsFirstOnFirstLoadedPage ) {
64+ previousNumItemsPrepended . current = prependedMessageCount ;
65+ return prependedMessageCount ;
5866 }
5967 }
6068
6169 // if no match has found, we have jumped - reset the prepended item count.
62- firstMessageId . current = currentFirstMessageId ;
70+ firstMessageOnFirstLoadedPage . current = currentFirstMessage ;
6371 previousNumItemsPrepended . current = 0 ;
6472 return 0 ;
6573 // TODO: there's a bug here, the messages prop is the same array instance (something mutates it)
6674 // that's why the second dependency is necessary
67- } , [ messages , messages ?. length ] ) ;
75+ } , [ firstRealMessageIndex , messages , messages ?. length ] ) ;
6876
6977 return numItemsPrepended ;
7078}
0 commit comments