stream-chat-react-native-core 8.13.12 → 8.13.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteSuggestionList.js +3 -2
  2. package/lib/commonjs/components/AutoCompleteInput/AutoCompleteSuggestionList.js.map +1 -1
  3. package/lib/commonjs/components/Channel/Channel.js +18 -2
  4. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  5. package/lib/commonjs/components/Channel/hooks/useCreateChannelContext.js +2 -0
  6. package/lib/commonjs/components/Channel/hooks/useCreateChannelContext.js.map +1 -1
  7. package/lib/commonjs/components/Chat/Chat.js +1 -1
  8. package/lib/commonjs/components/Chat/Chat.js.map +1 -1
  9. package/lib/commonjs/components/MessageList/MessageFlashList.js +43 -32
  10. package/lib/commonjs/components/MessageList/MessageFlashList.js.map +1 -1
  11. package/lib/commonjs/contexts/channelContext/ChannelContext.js.map +1 -1
  12. package/lib/commonjs/version.json +1 -1
  13. package/lib/module/components/AutoCompleteInput/AutoCompleteSuggestionList.js +3 -2
  14. package/lib/module/components/AutoCompleteInput/AutoCompleteSuggestionList.js.map +1 -1
  15. package/lib/module/components/Channel/Channel.js +18 -2
  16. package/lib/module/components/Channel/Channel.js.map +1 -1
  17. package/lib/module/components/Channel/hooks/useCreateChannelContext.js +2 -0
  18. package/lib/module/components/Channel/hooks/useCreateChannelContext.js.map +1 -1
  19. package/lib/module/components/Chat/Chat.js +1 -1
  20. package/lib/module/components/Chat/Chat.js.map +1 -1
  21. package/lib/module/components/MessageList/MessageFlashList.js +43 -32
  22. package/lib/module/components/MessageList/MessageFlashList.js.map +1 -1
  23. package/lib/module/contexts/channelContext/ChannelContext.js.map +1 -1
  24. package/lib/module/version.json +1 -1
  25. package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
  26. package/lib/typescript/components/Channel/hooks/useCreateChannelContext.d.ts +1 -1
  27. package/lib/typescript/components/Channel/hooks/useCreateChannelContext.d.ts.map +1 -1
  28. package/lib/typescript/components/Chat/Chat.d.ts.map +1 -1
  29. package/lib/typescript/components/MessageList/MessageFlashList.d.ts +1 -1
  30. package/lib/typescript/components/MessageList/MessageFlashList.d.ts.map +1 -1
  31. package/lib/typescript/contexts/channelContext/ChannelContext.d.ts +6 -0
  32. package/lib/typescript/contexts/channelContext/ChannelContext.d.ts.map +1 -1
  33. package/package.json +2 -2
  34. package/src/__tests__/offline-support/offline-feature.js +10 -2
  35. package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx +2 -1
  36. package/src/components/Channel/Channel.tsx +21 -6
  37. package/src/components/Channel/hooks/useCreateChannelContext.ts +2 -0
  38. package/src/components/Chat/Chat.tsx +4 -5
  39. package/src/components/MessageList/MessageFlashList.tsx +31 -25
  40. package/src/contexts/channelContext/ChannelContext.tsx +6 -0
  41. package/src/version.json +1 -1
@@ -834,6 +834,18 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
834
834
  channel,
835
835
  });
836
836
 
837
+ const shouldLoadInitialChannelAtFirstUnreadMessage = useStableCallback((unreadCount?: number) => {
838
+ if (messageId || !initialScrollToFirstUnreadMessage || !client.user) {
839
+ return false;
840
+ }
841
+
842
+ return (unreadCount ?? channel.countUnread()) > scrollToFirstUnreadThreshold;
843
+ });
844
+
845
+ const hasPendingInitialTargetLoad = useStableCallback(() => {
846
+ return !!messageId || shouldLoadInitialChannelAtFirstUnreadMessage();
847
+ });
848
+
837
849
  const { setMessages: copyMessagesStateFromChannel, viewabilityChangedCallback } =
838
850
  usePrunableMessageList({ maximumMessageLimit, setMessages: rawCopyMessagesStateFromChannel });
839
851
 
@@ -960,6 +972,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
960
972
  const initChannel = async () => {
961
973
  setLastRead(new Date());
962
974
  const unreadCount = channel.countUnread();
975
+ const shouldLoadAtFirstUnread = shouldLoadInitialChannelAtFirstUnreadMessage(unreadCount);
963
976
  if (!channel || !shouldSyncChannel) {
964
977
  return;
965
978
  }
@@ -989,13 +1002,14 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
989
1002
 
990
1003
  if (messageId) {
991
1004
  await loadChannelAroundMessage({ messageId, setTargetedMessage });
992
- } else if (
993
- initialScrollToFirstUnreadMessage &&
994
- client.user &&
995
- unreadCount > scrollToFirstUnreadThreshold
996
- ) {
1005
+ } else if (shouldLoadAtFirstUnread) {
1006
+ const clientUserId = client.user?.id;
1007
+ if (!clientUserId) {
1008
+ return;
1009
+ }
1010
+
997
1011
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
998
- const { user, ...ownReadState } = channel.state.read[client.user.id];
1012
+ const { user, ...ownReadState } = channel.state.read[clientUserId];
999
1013
 
1000
1014
  await loadChannelAtFirstUnreadMessage({
1001
1015
  channelUnreadState: ownReadState,
@@ -1788,6 +1802,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
1788
1802
  enableMessageGroupingByUser,
1789
1803
  enforceUniqueReaction,
1790
1804
  error,
1805
+ hasPendingInitialTargetLoad,
1791
1806
  hideDateSeparators,
1792
1807
  hideStickyDateHeader,
1793
1808
  highlightedMessageId,
@@ -31,6 +31,7 @@ export const useCreateChannelContext = ({
31
31
  setChannelUnreadState,
32
32
  setLastRead,
33
33
  setTargetedMessage,
34
+ hasPendingInitialTargetLoad,
34
35
  StickyHeader,
35
36
  targetedMessage,
36
37
  threadList,
@@ -58,6 +59,7 @@ export const useCreateChannelContext = ({
58
59
  enableMessageGroupingByUser,
59
60
  enforceUniqueReaction,
60
61
  error,
62
+ hasPendingInitialTargetLoad,
61
63
  hideDateSeparators,
62
64
  hideStickyDateHeader,
63
65
  highlightedMessageId,
@@ -1,7 +1,7 @@
1
1
  import React, { PropsWithChildren, useEffect, useMemo, useState } from 'react';
2
2
  import { Image, Platform } from 'react-native';
3
3
 
4
- import { Channel, OfflineDBState, SdkIdentifier } from 'stream-chat';
4
+ import { Channel, OfflineDBState } from 'stream-chat';
5
5
 
6
6
  import { useAppSettings } from './hooks/useAppSettings';
7
7
  import { useCreateChatContext } from './hooks/useCreateChatContext';
@@ -181,10 +181,9 @@ const ChatWithContext = (props: PropsWithChildren<ChatProps>) => {
181
181
 
182
182
  useEffect(() => {
183
183
  if (client) {
184
- const sdkName = (((NativeHandlers.SDK
185
- ? NativeHandlers.SDK.replace('stream-chat-', '')
186
- : 'react-native') as 'react-native' | 'expo') +
187
- `-${Platform.OS as 'ios' | 'android'}`) as SdkIdentifier['name'];
184
+ const sdkName = (
185
+ NativeHandlers.SDK ? NativeHandlers.SDK.replace('stream-chat-', '') : 'react-native'
186
+ ) as 'react-native' | 'expo';
188
187
  client.sdkIdentifier = {
189
188
  name: sdkName,
190
189
  version,
@@ -119,6 +119,7 @@ type MessageFlashListPropsWithContext = Pick<
119
119
  | 'scrollToFirstUnreadThreshold'
120
120
  | 'setChannelUnreadState'
121
121
  | 'setTargetedMessage'
122
+ | 'hasPendingInitialTargetLoad'
122
123
  | 'StickyHeader'
123
124
  | 'targetedMessage'
124
125
  | 'threadList'
@@ -298,6 +299,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
298
299
  setMessages,
299
300
  setSelectedPicker,
300
301
  setTargetedMessage,
302
+ hasPendingInitialTargetLoad,
301
303
  StickyHeader,
302
304
  targetedMessage,
303
305
  thread,
@@ -389,11 +391,15 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
389
391
 
390
392
  useEffect(() => {
391
393
  if (autoscrollToRecent && flashListRef.current) {
394
+ if (hasPendingInitialTargetLoad?.()) {
395
+ return;
396
+ }
397
+
392
398
  flashListRef.current.scrollToEnd({
393
399
  animated: true,
394
400
  });
395
401
  }
396
- }, [autoscrollToRecent]);
402
+ }, [autoscrollToRecent, hasPendingInitialTargetLoad]);
397
403
 
398
404
  const maintainVisibleContentPosition = useMemo(() => {
399
405
  return {
@@ -409,18 +415,6 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
409
415
  }
410
416
  }, [disabled]);
411
417
 
412
- const indexToScrollToRef = useRef<number | undefined>(undefined);
413
-
414
- const initialIndexToScrollTo = useMemo(() => {
415
- return targetedMessage
416
- ? processedMessageList.findIndex((message) => message?.id === targetedMessage)
417
- : -1;
418
- }, [processedMessageList, targetedMessage]);
419
-
420
- useEffect(() => {
421
- indexToScrollToRef.current = initialIndexToScrollTo;
422
- }, [initialIndexToScrollTo]);
423
-
424
418
  /**
425
419
  * Check if a messageId needs to be scrolled to after list loads, and scroll to it
426
420
  * Note: This effect fires on every list change with a small debounce so that scrolling isnt abrupted by an immediate rerender
@@ -441,13 +435,29 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
441
435
  scrollToDebounceTimeoutRef.current = setTimeout(() => {
442
436
  clearTimeout(scrollToDebounceTimeoutRef.current);
443
437
 
444
- // now scroll to it
445
- flashListRef.current?.scrollToIndex({
446
- animated: true,
447
- index: indexOfParentInMessageList,
448
- viewPosition: 0.5,
438
+ const scrollToIndex = async () => {
439
+ const list = flashListRef.current;
440
+
441
+ if (!list) {
442
+ return false;
443
+ }
444
+
445
+ await list.scrollToIndex({
446
+ animated: true,
447
+ index: indexOfParentInMessageList,
448
+ viewPosition: 0.5,
449
+ });
450
+
451
+ return true;
452
+ };
453
+
454
+ requestAnimationFrame(async () => {
455
+ await scrollToIndex();
456
+ requestAnimationFrame(async () => {
457
+ await scrollToIndex();
458
+ setTargetedMessage(undefined);
459
+ });
449
460
  });
450
- setTargetedMessage(undefined);
451
461
  }, WAIT_FOR_SCROLL_TIMEOUT);
452
462
  }
453
463
  }, [loadChannelAroundMessage, processedMessageList, setTargetedMessage, targetedMessage]);
@@ -457,8 +467,6 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
457
467
  (message) => message?.id === messageId,
458
468
  );
459
469
 
460
- indexToScrollToRef.current = indexOfParentInMessageList;
461
-
462
470
  try {
463
471
  if (indexOfParentInMessageList === -1) {
464
472
  clearTimeout(scrollToDebounceTimeoutRef.current);
@@ -530,7 +538,6 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
530
538
  setScrollToBottomButtonVisible(true);
531
539
  return;
532
540
  } else {
533
- indexToScrollToRef.current = undefined;
534
541
  setAutoscrollToRecent(true);
535
542
  }
536
543
  const latestNonCurrentMessageBeforeUpdate = latestNonCurrentMessageBeforeUpdateRef.current;
@@ -1072,9 +1079,6 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
1072
1079
  data={processedMessageList}
1073
1080
  drawDistance={800}
1074
1081
  getItemType={getItemTypeInternal}
1075
- initialScrollIndex={
1076
- indexToScrollToRef.current === -1 ? undefined : indexToScrollToRef.current
1077
- }
1078
1082
  keyboardShouldPersistTaps='handled'
1079
1083
  keyExtractor={keyExtractor}
1080
1084
  ListFooterComponent={FooterComponent}
@@ -1151,6 +1155,7 @@ export const MessageFlashList = (props: MessageFlashListProps) => {
1151
1155
  scrollToFirstUnreadThreshold,
1152
1156
  setChannelUnreadState,
1153
1157
  setTargetedMessage,
1158
+ hasPendingInitialTargetLoad,
1154
1159
  StickyHeader,
1155
1160
  targetedMessage,
1156
1161
  threadList,
@@ -1192,6 +1197,7 @@ export const MessageFlashList = (props: MessageFlashListProps) => {
1192
1197
  enableMessageGroupingByUser,
1193
1198
  error,
1194
1199
  FlatList,
1200
+ hasPendingInitialTargetLoad,
1195
1201
  hideStickyDateHeader,
1196
1202
  highlightedMessageId,
1197
1203
  InlineDateSeparator,
@@ -130,6 +130,12 @@ export type ChannelContextValue = {
130
130
  setChannelUnreadState: (data: ChannelUnreadStateStoreType['channelUnreadState']) => void;
131
131
  setLastRead: React.Dispatch<React.SetStateAction<Date | undefined>>;
132
132
  setTargetedMessage: (messageId?: string) => void;
133
+ /**
134
+ * Returns true when Channel is about to load an initial targeted message.
135
+ *
136
+ * @internal
137
+ */
138
+ hasPendingInitialTargetLoad?: () => boolean;
133
139
  /**
134
140
  * Abort controller for cancelling async requests made for uploading images/files
135
141
  * Its a map of filename and AbortController
package/src/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "8.13.12"
2
+ "version": "8.13.14"
3
3
  }