stream-chat-react-native-core 6.7.3-beta.1 → 6.7.3-beta.3

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 (64) hide show
  1. package/lib/commonjs/components/Channel/Channel.js +296 -293
  2. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  3. package/lib/commonjs/components/Channel/hooks/useMessageListPagination.js +133 -147
  4. package/lib/commonjs/components/Channel/hooks/useMessageListPagination.js.map +1 -1
  5. package/lib/commonjs/components/KeyboardCompatibleView/KeyboardCompatibleView.js +7 -12
  6. package/lib/commonjs/components/KeyboardCompatibleView/KeyboardCompatibleView.js.map +1 -1
  7. package/lib/commonjs/components/MessageList/MessageList.js +167 -179
  8. package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
  9. package/lib/commonjs/components/MessageList/hooks/useMessageList.js +60 -37
  10. package/lib/commonjs/components/MessageList/hooks/useMessageList.js.map +1 -1
  11. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +450 -459
  12. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  13. package/lib/commonjs/contexts/messagesContext/MessagesContext.js.map +1 -1
  14. package/lib/commonjs/hooks/index.js +11 -0
  15. package/lib/commonjs/hooks/index.js.map +1 -1
  16. package/lib/commonjs/hooks/useStableCallback.js +13 -0
  17. package/lib/commonjs/hooks/useStableCallback.js.map +1 -0
  18. package/lib/commonjs/version.json +1 -1
  19. package/lib/module/components/Channel/Channel.js +296 -293
  20. package/lib/module/components/Channel/Channel.js.map +1 -1
  21. package/lib/module/components/Channel/hooks/useMessageListPagination.js +133 -147
  22. package/lib/module/components/Channel/hooks/useMessageListPagination.js.map +1 -1
  23. package/lib/module/components/KeyboardCompatibleView/KeyboardCompatibleView.js +7 -12
  24. package/lib/module/components/KeyboardCompatibleView/KeyboardCompatibleView.js.map +1 -1
  25. package/lib/module/components/MessageList/MessageList.js +167 -179
  26. package/lib/module/components/MessageList/MessageList.js.map +1 -1
  27. package/lib/module/components/MessageList/hooks/useMessageList.js +60 -37
  28. package/lib/module/components/MessageList/hooks/useMessageList.js.map +1 -1
  29. package/lib/module/contexts/messageInputContext/MessageInputContext.js +450 -459
  30. package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  31. package/lib/module/contexts/messagesContext/MessagesContext.js.map +1 -1
  32. package/lib/module/hooks/index.js +11 -0
  33. package/lib/module/hooks/index.js.map +1 -1
  34. package/lib/module/hooks/useStableCallback.js +13 -0
  35. package/lib/module/hooks/useStableCallback.js.map +1 -0
  36. package/lib/module/version.json +1 -1
  37. package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
  38. package/lib/typescript/components/Channel/hooks/useMessageListPagination.d.ts +3 -3
  39. package/lib/typescript/components/Channel/hooks/useMessageListPagination.d.ts.map +1 -1
  40. package/lib/typescript/components/KeyboardCompatibleView/KeyboardCompatibleView.d.ts +3 -0
  41. package/lib/typescript/components/KeyboardCompatibleView/KeyboardCompatibleView.d.ts.map +1 -1
  42. package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
  43. package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts +4 -0
  44. package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts.map +1 -1
  45. package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts.map +1 -1
  46. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts +1 -1
  47. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts.map +1 -1
  48. package/lib/typescript/hooks/index.d.ts +1 -0
  49. package/lib/typescript/hooks/index.d.ts.map +1 -1
  50. package/lib/typescript/hooks/useStableCallback.d.ts +26 -0
  51. package/lib/typescript/hooks/useStableCallback.d.ts.map +1 -0
  52. package/package.json +1 -1
  53. package/src/components/Channel/Channel.tsx +462 -431
  54. package/src/components/Channel/__tests__/Channel.test.js +8 -3
  55. package/src/components/Channel/hooks/useMessageListPagination.tsx +152 -147
  56. package/src/components/KeyboardCompatibleView/KeyboardCompatibleView.tsx +6 -4
  57. package/src/components/MessageList/MessageList.tsx +147 -112
  58. package/src/components/MessageList/hooks/useMessageList.ts +69 -38
  59. package/src/contexts/messageInputContext/MessageInputContext.tsx +293 -267
  60. package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx +2 -1
  61. package/src/contexts/messagesContext/MessagesContext.tsx +1 -0
  62. package/src/hooks/index.ts +1 -0
  63. package/src/hooks/useStableCallback.ts +37 -0
  64. package/src/version.json +1 -1
@@ -74,6 +74,7 @@ import {
74
74
  useTranslationContext,
75
75
  } from '../../contexts/translationContext/TranslationContext';
76
76
  import { TypingProvider } from '../../contexts/typingContext/TypingContext';
77
+ import { useStableCallback } from '../../hooks';
77
78
  import { useAppStateListener } from '../../hooks/useAppStateListener';
78
79
 
79
80
  import {
@@ -716,6 +717,12 @@ const ChannelWithContext = <
716
717
  * Its a map of filename to AbortController
717
718
  */
718
719
  const uploadAbortControllerRef = useRef<Map<string, AbortController>>(new Map());
720
+ /**
721
+ * This ref keeps track of message IDs which have already been optimistically updated.
722
+ * We need it to make sure we don't react on message.new/notification.message_new events
723
+ * if this is indeed the case, as it's a full list update for nothing.
724
+ */
725
+ const optimisticallyUpdatedNewMessages = useMemo<Set<string>>(() => new Set(), []);
719
726
 
720
727
  const channelId = channel?.id || '';
721
728
  const pollCreationEnabled = !channel.disconnected && !!channel?.id && channel?.getConfig()?.polls;
@@ -741,14 +748,33 @@ const ChannelWithContext = <
741
748
  channel,
742
749
  });
743
750
 
744
- /**
745
- * Since we copy the current channel state all together, we need to find the greatest time among the below two and apply it as the throttling time for copying the channel state.
746
- * This is done until we remove the newMessageStateUpdateThrottleInterval prop.
747
- */
748
- const copyChannelStateThrottlingTime =
749
- newMessageStateUpdateThrottleInterval > stateUpdateThrottleInterval
750
- ? newMessageStateUpdateThrottleInterval
751
- : stateUpdateThrottleInterval;
751
+ const setReadThrottled = useMemo(
752
+ () =>
753
+ throttle(
754
+ () => {
755
+ if (channel) {
756
+ setRead(channel);
757
+ }
758
+ },
759
+ stateUpdateThrottleInterval,
760
+ throttleOptions,
761
+ ),
762
+ [channel, stateUpdateThrottleInterval, setRead],
763
+ );
764
+
765
+ const copyMessagesStateFromChannelThrottled = useMemo(
766
+ () =>
767
+ throttle(
768
+ () => {
769
+ if (channel) {
770
+ copyMessagesStateFromChannel(channel);
771
+ }
772
+ },
773
+ newMessageStateUpdateThrottleInterval,
774
+ throttleOptions,
775
+ ),
776
+ [channel, newMessageStateUpdateThrottleInterval, copyMessagesStateFromChannel],
777
+ );
752
778
 
753
779
  const copyChannelState = useMemo(
754
780
  () =>
@@ -759,13 +785,13 @@ const ChannelWithContext = <
759
785
  copyMessagesStateFromChannel(channel);
760
786
  }
761
787
  },
762
- copyChannelStateThrottlingTime,
788
+ stateUpdateThrottleInterval,
763
789
  throttleOptions,
764
790
  ),
765
- [channel, copyChannelStateThrottlingTime, copyMessagesStateFromChannel, copyStateFromChannel],
791
+ [stateUpdateThrottleInterval, channel, copyStateFromChannel, copyMessagesStateFromChannel],
766
792
  );
767
793
 
768
- const handleEvent: EventHandler<StreamChatGenerics> = (event) => {
794
+ const handleEvent: EventHandler<StreamChatGenerics> = useStableCallback((event) => {
769
795
  if (shouldSyncChannel) {
770
796
  /**
771
797
  * Ignore user.watching.start and user.watching.stop as we should not copy the entire state when
@@ -819,17 +845,35 @@ const ChannelWithContext = <
819
845
 
820
846
  // only update channel state if the events are not the previously subscribed useEffect's subscription events
821
847
  if (channel && channel.initialized) {
848
+ // we skip the new message events if we've already done an optimistic update for the new message
849
+ if (event.type === 'message.new' || event.type === 'notification.message_new') {
850
+ const messageId = event.message?.id ?? '';
851
+ if (
852
+ event.user?.id !== client.userID ||
853
+ !optimisticallyUpdatedNewMessages.has(messageId)
854
+ ) {
855
+ copyMessagesStateFromChannelThrottled();
856
+ }
857
+ optimisticallyUpdatedNewMessages.delete(messageId);
858
+ return;
859
+ }
860
+
861
+ if (event.type === 'message.read' || event.type === 'notification.mark_read') {
862
+ setReadThrottled();
863
+ return;
864
+ }
865
+
822
866
  copyChannelState();
823
867
  }
824
868
  }
825
- };
869
+ });
826
870
 
827
871
  useEffect(() => {
828
872
  let listener: ReturnType<typeof channel.on>;
829
873
  const initChannel = async () => {
830
874
  setLastRead(new Date());
831
875
  const unreadCount = channel.countUnread();
832
- if (!channel || !shouldSyncChannel || channel.offlineMode) {
876
+ if (!channel || !shouldSyncChannel) {
833
877
  return;
834
878
  }
835
879
  let errored = false;
@@ -900,20 +944,6 @@ const ChannelWithContext = <
900
944
  return unsubscribe;
901
945
  }, [channel?.cid, client]);
902
946
 
903
- /**
904
- * Subscription to the Notification mark_read event.
905
- */
906
- useEffect(() => {
907
- const handleEvent: EventHandler<StreamChatGenerics> = (event) => {
908
- if (channel.cid === event.cid) {
909
- setRead(channel);
910
- }
911
- };
912
-
913
- const { unsubscribe } = client.on('notification.mark_read', handleEvent);
914
- return unsubscribe;
915
- }, [channel, client, setRead]);
916
-
917
947
  const threadPropsExists = !!threadProps;
918
948
 
919
949
  useEffect(() => {
@@ -946,7 +976,7 @@ const ChannelWithContext = <
946
976
  /**
947
977
  * CHANNEL METHODS
948
978
  */
949
- const markRead: ChannelContextValue<StreamChatGenerics>['markRead'] = throttle(
979
+ const markReadInternal: ChannelContextValue<StreamChatGenerics>['markRead'] = throttle(
950
980
  async (options?: MarkReadFunctionOptions) => {
951
981
  const { updateChannelUnreadState = true } = options ?? {};
952
982
  if (!channel || channel?.disconnected || !clientChannelConfig?.read_events) {
@@ -975,7 +1005,9 @@ const ChannelWithContext = <
975
1005
  throttleOptions,
976
1006
  );
977
1007
 
978
- const reloadThread = async () => {
1008
+ const markRead = useStableCallback(markReadInternal);
1009
+
1010
+ const reloadThread = useStableCallback(async () => {
979
1011
  if (!channel || !thread?.id) {
980
1012
  return;
981
1013
  }
@@ -1008,9 +1040,9 @@ const ChannelWithContext = <
1008
1040
  setThreadLoadingMore(false);
1009
1041
  throw err;
1010
1042
  }
1011
- };
1043
+ });
1012
1044
 
1013
- const resyncChannel = async () => {
1045
+ const resyncChannel = useStableCallback(async () => {
1014
1046
  if (!channel || syncingChannelRef.current) {
1015
1047
  return;
1016
1048
  }
@@ -1066,7 +1098,7 @@ const ChannelWithContext = <
1066
1098
  }
1067
1099
 
1068
1100
  syncingChannelRef.current = false;
1069
- };
1101
+ });
1070
1102
 
1071
1103
  // resync channel is added to ref so that it can be used in useEffect without adding it as a dependency
1072
1104
  const resyncChannelRef = useRef(resyncChannel);
@@ -1117,16 +1149,16 @@ const ChannelWithContext = <
1117
1149
  */
1118
1150
  const clientChannelConfig = getChannelConfigSafely();
1119
1151
 
1120
- const reloadChannel = async () => {
1152
+ const reloadChannel = useStableCallback(async () => {
1121
1153
  try {
1122
1154
  await loadLatestMessages();
1123
1155
  } catch (err) {
1124
1156
  console.warn('Reloading channel failed with error:', err);
1125
1157
  }
1126
- };
1158
+ });
1127
1159
 
1128
1160
  const loadChannelAroundMessage: ChannelContextValue<StreamChatGenerics>['loadChannelAroundMessage'] =
1129
- async ({ messageId: messageIdToLoadAround }): Promise<void> => {
1161
+ useStableCallback(async ({ messageId: messageIdToLoadAround }): Promise<void> => {
1130
1162
  if (!messageIdToLoadAround) {
1131
1163
  return;
1132
1164
  }
@@ -1157,350 +1189,354 @@ const ChannelWithContext = <
1157
1189
  } catch (err) {
1158
1190
  console.warn('Loading channel around message failed with error:', err);
1159
1191
  }
1160
- };
1192
+ });
1161
1193
 
1162
1194
  /**
1163
1195
  * MESSAGE METHODS
1164
1196
  */
1165
- const updateMessage: MessagesContextValue<StreamChatGenerics>['updateMessage'] = (
1166
- updatedMessage,
1167
- extraState = {},
1168
- ) => {
1169
- if (!channel) {
1170
- return;
1171
- }
1197
+ const updateMessage: MessagesContextValue<StreamChatGenerics>['updateMessage'] =
1198
+ useStableCallback((updatedMessage, extraState = {}, throttled = false) => {
1199
+ if (!channel) {
1200
+ return;
1201
+ }
1172
1202
 
1173
- channel.state.addMessageSorted(updatedMessage, true);
1174
- copyMessagesStateFromChannel(channel);
1203
+ channel.state.addMessageSorted(updatedMessage, true);
1204
+ if (throttled) {
1205
+ copyMessagesStateFromChannelThrottled();
1206
+ } else {
1207
+ copyMessagesStateFromChannel(channel);
1208
+ }
1175
1209
 
1176
- if (thread && updatedMessage.parent_id) {
1177
- extraState.threadMessages = channel.state.threads[updatedMessage.parent_id] || [];
1178
- setThreadMessages(extraState.threadMessages);
1179
- }
1180
- };
1210
+ if (thread && updatedMessage.parent_id) {
1211
+ extraState.threadMessages = channel.state.threads[updatedMessage.parent_id] || [];
1212
+ setThreadMessages(extraState.threadMessages);
1213
+ }
1214
+ });
1181
1215
 
1182
- const replaceMessage = (
1183
- oldMessage: MessageResponse<StreamChatGenerics>,
1184
- newMessage: MessageResponse<StreamChatGenerics>,
1185
- ) => {
1186
- if (channel) {
1187
- channel.state.removeMessage(oldMessage);
1188
- channel.state.addMessageSorted(newMessage, true);
1189
- copyMessagesStateFromChannel(channel);
1216
+ const replaceMessage = useStableCallback(
1217
+ (
1218
+ oldMessage: MessageResponse<StreamChatGenerics>,
1219
+ newMessage: MessageResponse<StreamChatGenerics>,
1220
+ ) => {
1221
+ if (channel) {
1222
+ channel.state.removeMessage(oldMessage);
1223
+ channel.state.addMessageSorted(newMessage, true);
1224
+ copyMessagesStateFromChannel(channel);
1190
1225
 
1191
- if (thread && newMessage.parent_id) {
1192
- const threadMessages = channel.state.threads[newMessage.parent_id] || [];
1193
- setThreadMessages(threadMessages);
1226
+ if (thread && newMessage.parent_id) {
1227
+ const threadMessages = channel.state.threads[newMessage.parent_id] || [];
1228
+ setThreadMessages(threadMessages);
1229
+ }
1194
1230
  }
1195
- }
1196
- };
1231
+ },
1232
+ );
1197
1233
 
1198
- const createMessagePreview = ({
1199
- attachments,
1200
- mentioned_users,
1201
- parent_id,
1202
- poll,
1203
- poll_id,
1204
- text,
1205
- ...extraFields
1206
- }: Partial<StreamMessage<StreamChatGenerics>>) => {
1207
- // Exclude following properties from message.user within message preview,
1208
- // since they could be long arrays and have no meaning as sender of message.
1209
- // Storing such large value within user's table may cause sqlite queries to crash.
1210
- // @ts-ignore
1211
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1212
- const { channel_mutes, devices, mutes, ...messageUser } = client.user;
1213
-
1214
- const preview = {
1215
- __html: text,
1234
+ const createMessagePreview = useStableCallback(
1235
+ ({
1216
1236
  attachments,
1217
- created_at: new Date(),
1218
- html: text,
1219
- id: `${client.userID}-${generateRandomId()}`,
1220
- mentioned_users:
1221
- mentioned_users?.map((userId) => ({
1222
- id: userId,
1223
- })) || [],
1237
+ mentioned_users,
1224
1238
  parent_id,
1225
1239
  poll,
1226
1240
  poll_id,
1227
- reactions: [],
1228
- status: MessageStatusTypes.SENDING,
1229
1241
  text,
1230
- type: 'regular',
1231
- user: {
1232
- ...messageUser,
1233
- id: client.userID,
1234
- },
1235
- ...extraFields,
1236
- } as unknown as MessageResponse<StreamChatGenerics>;
1237
-
1238
- /**
1239
- * This is added to the message for local rendering prior to the message
1240
- * being returned from the backend, it is removed when the message is sent
1241
- * as quoted_message is a reserved field.
1242
- */
1243
- if (preview.quoted_message_id) {
1244
- const quotedMessage = channelMessagesState.messages?.find(
1245
- (message) => message.id === preview.quoted_message_id,
1246
- );
1242
+ ...extraFields
1243
+ }: Partial<StreamMessage<StreamChatGenerics>>) => {
1244
+ // Exclude following properties from message.user within message preview,
1245
+ // since they could be long arrays and have no meaning as sender of message.
1246
+ // Storing such large value within user's table may cause sqlite queries to crash.
1247
+ // @ts-ignore
1248
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1249
+ const { channel_mutes, devices, mutes, ...messageUser } = client.user;
1250
+
1251
+ const preview = {
1252
+ __html: text,
1253
+ attachments,
1254
+ created_at: new Date(),
1255
+ html: text,
1256
+ id: `${client.userID}-${generateRandomId()}`,
1257
+ mentioned_users:
1258
+ mentioned_users?.map((userId) => ({
1259
+ id: userId,
1260
+ })) || [],
1261
+ parent_id,
1262
+ poll,
1263
+ poll_id,
1264
+ reactions: [],
1265
+ status: MessageStatusTypes.SENDING,
1266
+ text,
1267
+ type: 'regular',
1268
+ user: {
1269
+ ...messageUser,
1270
+ id: client.userID,
1271
+ },
1272
+ ...extraFields,
1273
+ } as unknown as MessageResponse<StreamChatGenerics>;
1247
1274
 
1248
- preview.quoted_message =
1249
- quotedMessage as MessageResponse<StreamChatGenerics>['quoted_message'];
1250
- }
1251
- return preview;
1252
- };
1275
+ /**
1276
+ * This is added to the message for local rendering prior to the message
1277
+ * being returned from the backend, it is removed when the message is sent
1278
+ * as quoted_message is a reserved field.
1279
+ */
1280
+ if (preview.quoted_message_id) {
1281
+ const quotedMessage = channelMessagesState.messages?.find(
1282
+ (message) => message.id === preview.quoted_message_id,
1283
+ );
1253
1284
 
1254
- const uploadPendingAttachments = async (message: MessageResponse<StreamChatGenerics>) => {
1255
- const updatedMessage = { ...message };
1256
- if (updatedMessage.attachments?.length) {
1257
- for (let i = 0; i < updatedMessage.attachments?.length; i++) {
1258
- const attachment = updatedMessage.attachments[i];
1259
- const image = attachment.originalImage;
1260
- const file = attachment.originalFile;
1261
- // check if image_url is not a remote url
1262
- if (
1263
- attachment.type === FileTypes.Image &&
1264
- image?.uri &&
1265
- attachment.image_url &&
1266
- isLocalUrl(attachment.image_url)
1267
- ) {
1268
- const filename = image.name ?? getFileNameFromPath(image.uri);
1269
- // if any upload is in progress, cancel it
1270
- const controller = uploadAbortControllerRef.current.get(filename);
1271
- if (controller) {
1272
- controller.abort();
1273
- uploadAbortControllerRef.current.delete(filename);
1274
- }
1275
- const compressedUri = await compressedImageURI(image, compressImageQuality);
1276
- const contentType = lookup(filename) || 'multipart/form-data';
1285
+ preview.quoted_message =
1286
+ quotedMessage as MessageResponse<StreamChatGenerics>['quoted_message'];
1287
+ }
1288
+ return preview;
1289
+ },
1290
+ );
1277
1291
 
1278
- const uploadResponse = doImageUploadRequest
1279
- ? await doImageUploadRequest(image, channel)
1280
- : await channel.sendImage(compressedUri, filename, contentType);
1292
+ const uploadPendingAttachments = useStableCallback(
1293
+ async (message: MessageResponse<StreamChatGenerics>) => {
1294
+ const updatedMessage = { ...message };
1295
+ if (updatedMessage.attachments?.length) {
1296
+ for (let i = 0; i < updatedMessage.attachments?.length; i++) {
1297
+ const attachment = updatedMessage.attachments[i];
1298
+ const image = attachment.originalImage;
1299
+ const file = attachment.originalFile;
1300
+ // check if image_url is not a remote url
1301
+ if (
1302
+ attachment.type === FileTypes.Image &&
1303
+ image?.uri &&
1304
+ attachment.image_url &&
1305
+ isLocalUrl(attachment.image_url)
1306
+ ) {
1307
+ const filename = image.name ?? getFileNameFromPath(image.uri);
1308
+ // if any upload is in progress, cancel it
1309
+ const controller = uploadAbortControllerRef.current.get(filename);
1310
+ if (controller) {
1311
+ controller.abort();
1312
+ uploadAbortControllerRef.current.delete(filename);
1313
+ }
1314
+ const compressedUri = await compressedImageURI(image, compressImageQuality);
1315
+ const contentType = lookup(filename) || 'multipart/form-data';
1281
1316
 
1282
- attachment.image_url = uploadResponse.file;
1283
- delete attachment.originalFile;
1317
+ const uploadResponse = doImageUploadRequest
1318
+ ? await doImageUploadRequest(image, channel)
1319
+ : await channel.sendImage(compressedUri, filename, contentType);
1284
1320
 
1285
- await dbApi.updateMessage({
1286
- message: { ...updatedMessage, cid: channel.cid },
1287
- });
1288
- }
1321
+ attachment.image_url = uploadResponse.file;
1322
+ delete attachment.originalFile;
1289
1323
 
1290
- if (
1291
- (attachment.type === FileTypes.File ||
1292
- attachment.type === FileTypes.Audio ||
1293
- attachment.type === FileTypes.VoiceRecording ||
1294
- attachment.type === FileTypes.Video) &&
1295
- attachment.asset_url &&
1296
- isLocalUrl(attachment.asset_url) &&
1297
- file?.uri
1298
- ) {
1299
- // if any upload is in progress, cancel it
1300
- const controller = uploadAbortControllerRef.current.get(file.name);
1301
- if (controller) {
1302
- controller.abort();
1303
- uploadAbortControllerRef.current.delete(file.name);
1304
- }
1305
- const response = doDocUploadRequest
1306
- ? await doDocUploadRequest(file, channel)
1307
- : await channel.sendFile(file.uri, file.name, file.mimeType);
1308
- attachment.asset_url = response.file;
1309
- if (response.thumb_url) {
1310
- attachment.thumb_url = response.thumb_url;
1324
+ await dbApi.updateMessage({
1325
+ message: { ...updatedMessage, cid: channel.cid },
1326
+ });
1311
1327
  }
1312
1328
 
1313
- delete attachment.originalFile;
1314
- await dbApi.updateMessage({
1315
- message: { ...updatedMessage, cid: channel.cid },
1316
- });
1329
+ if (
1330
+ (attachment.type === FileTypes.File ||
1331
+ attachment.type === FileTypes.Audio ||
1332
+ attachment.type === FileTypes.VoiceRecording ||
1333
+ attachment.type === FileTypes.Video) &&
1334
+ attachment.asset_url &&
1335
+ isLocalUrl(attachment.asset_url) &&
1336
+ file?.uri
1337
+ ) {
1338
+ // if any upload is in progress, cancel it
1339
+ const controller = uploadAbortControllerRef.current.get(file.name);
1340
+ if (controller) {
1341
+ controller.abort();
1342
+ uploadAbortControllerRef.current.delete(file.name);
1343
+ }
1344
+ const response = doDocUploadRequest
1345
+ ? await doDocUploadRequest(file, channel)
1346
+ : await channel.sendFile(file.uri, file.name, file.mimeType);
1347
+ attachment.asset_url = response.file;
1348
+ if (response.thumb_url) {
1349
+ attachment.thumb_url = response.thumb_url;
1350
+ }
1351
+
1352
+ delete attachment.originalFile;
1353
+ await dbApi.updateMessage({
1354
+ message: { ...updatedMessage, cid: channel.cid },
1355
+ });
1356
+ }
1317
1357
  }
1318
1358
  }
1319
- }
1320
-
1321
- return updatedMessage;
1322
- };
1323
1359
 
1324
- const sendMessageRequest = async (
1325
- message: MessageResponse<StreamChatGenerics>,
1326
- retrying?: boolean,
1327
- ) => {
1328
- try {
1329
- const updatedMessage = await uploadPendingAttachments(message);
1330
- const extraFields = omit(updatedMessage, [
1331
- '__html',
1332
- 'attachments',
1333
- 'created_at',
1334
- 'deleted_at',
1335
- 'html',
1336
- 'id',
1337
- 'latest_reactions',
1338
- 'mentioned_users',
1339
- 'own_reactions',
1340
- 'parent_id',
1341
- 'quoted_message',
1342
- 'reaction_counts',
1343
- 'reaction_groups',
1344
- 'reactions',
1345
- 'status',
1346
- 'text',
1347
- 'type',
1348
- 'updated_at',
1349
- 'user',
1350
- ]);
1351
- const { attachments, id, mentioned_users, parent_id, text } = updatedMessage;
1352
- if (!channel.id) {
1353
- return;
1354
- }
1360
+ return updatedMessage;
1361
+ },
1362
+ );
1355
1363
 
1356
- const mentionedUserIds = mentioned_users?.map((user) => user.id) || [];
1364
+ const sendMessageRequest = useStableCallback(
1365
+ async (message: MessageResponse<StreamChatGenerics>, retrying?: boolean) => {
1366
+ try {
1367
+ const updatedMessage = await uploadPendingAttachments(message);
1368
+ const extraFields = omit(updatedMessage, [
1369
+ '__html',
1370
+ 'attachments',
1371
+ 'created_at',
1372
+ 'deleted_at',
1373
+ 'html',
1374
+ 'id',
1375
+ 'latest_reactions',
1376
+ 'mentioned_users',
1377
+ 'own_reactions',
1378
+ 'parent_id',
1379
+ 'quoted_message',
1380
+ 'reaction_counts',
1381
+ 'reaction_groups',
1382
+ 'reactions',
1383
+ 'status',
1384
+ 'text',
1385
+ 'type',
1386
+ 'updated_at',
1387
+ 'user',
1388
+ ]);
1389
+ const { attachments, id, mentioned_users, parent_id, text } = updatedMessage;
1390
+ if (!channel.id) {
1391
+ return;
1392
+ }
1357
1393
 
1358
- const messageData = {
1359
- attachments,
1360
- id,
1361
- mentioned_users: mentionedUserIds,
1362
- parent_id,
1363
- text: patchMessageTextCommand(text ?? '', mentionedUserIds),
1364
- ...extraFields,
1365
- } as StreamMessage<StreamChatGenerics>;
1394
+ const mentionedUserIds = mentioned_users?.map((user) => user.id) || [];
1395
+
1396
+ const messageData = {
1397
+ attachments,
1398
+ id,
1399
+ mentioned_users: mentionedUserIds,
1400
+ parent_id,
1401
+ text: patchMessageTextCommand(text ?? '', mentionedUserIds),
1402
+ ...extraFields,
1403
+ } as StreamMessage<StreamChatGenerics>;
1404
+
1405
+ let messageResponse = {} as SendMessageAPIResponse<StreamChatGenerics>;
1406
+ if (doSendMessageRequest) {
1407
+ messageResponse = await doSendMessageRequest(channel?.cid || '', messageData);
1408
+ } else if (channel) {
1409
+ messageResponse = await channel.sendMessage(messageData);
1410
+ }
1366
1411
 
1367
- let messageResponse = {} as SendMessageAPIResponse<StreamChatGenerics>;
1368
- if (doSendMessageRequest) {
1369
- messageResponse = await doSendMessageRequest(channel?.cid || '', messageData);
1370
- } else if (channel) {
1371
- messageResponse = await channel.sendMessage(messageData);
1372
- }
1412
+ if (messageResponse.message) {
1413
+ messageResponse.message.status = MessageStatusTypes.RECEIVED;
1373
1414
 
1374
- if (messageResponse.message) {
1375
- messageResponse.message.status = MessageStatusTypes.RECEIVED;
1415
+ if (enableOfflineSupport) {
1416
+ await dbApi.updateMessage({
1417
+ message: { ...messageResponse.message, cid: channel.cid },
1418
+ });
1419
+ }
1420
+ if (retrying) {
1421
+ replaceMessage(message, messageResponse.message);
1422
+ } else {
1423
+ updateMessage(messageResponse.message, {}, true);
1424
+ }
1425
+ }
1426
+ } catch (err) {
1427
+ console.log(err);
1428
+ message.status = MessageStatusTypes.FAILED;
1429
+ const updatedMessage = { ...message, cid: channel.cid };
1430
+ updateMessage(updatedMessage);
1431
+ threadInstance?.upsertReplyLocally?.({ message: updatedMessage });
1432
+ optimisticallyUpdatedNewMessages.delete(message.id);
1376
1433
 
1377
1434
  if (enableOfflineSupport) {
1378
1435
  await dbApi.updateMessage({
1379
- message: { ...messageResponse.message, cid: channel.cid },
1436
+ message: { ...message, cid: channel.cid },
1380
1437
  });
1381
1438
  }
1382
- if (retrying) {
1383
- replaceMessage(message, messageResponse.message);
1384
- } else {
1385
- updateMessage(messageResponse.message);
1386
- }
1387
1439
  }
1388
- } catch (err) {
1389
- console.log(err);
1390
- message.status = MessageStatusTypes.FAILED;
1391
- const updatedMessage = { ...message, cid: channel.cid };
1392
- updateMessage(updatedMessage);
1393
- threadInstance?.upsertReplyLocally?.({ message: updatedMessage });
1440
+ },
1441
+ );
1394
1442
 
1395
- if (enableOfflineSupport) {
1396
- await dbApi.updateMessage({
1397
- message: { ...message, cid: channel.cid },
1398
- });
1443
+ const sendMessage: InputMessageInputContextValue<StreamChatGenerics>['sendMessage'] =
1444
+ useStableCallback(async (message) => {
1445
+ if (channel?.state?.filterErrorMessages) {
1446
+ channel.state.filterErrorMessages();
1399
1447
  }
1400
- }
1401
- };
1402
1448
 
1403
- const sendMessage: InputMessageInputContextValue<StreamChatGenerics>['sendMessage'] = async (
1404
- message,
1405
- ) => {
1406
- if (channel?.state?.filterErrorMessages) {
1407
- channel.state.filterErrorMessages();
1408
- }
1449
+ const messagePreview = createMessagePreview({
1450
+ ...message,
1451
+ attachments: message.attachments || [],
1452
+ });
1409
1453
 
1410
- const messagePreview = createMessagePreview({
1411
- ...message,
1412
- attachments: message.attachments || [],
1413
- });
1454
+ updateMessage(messagePreview, {
1455
+ commands: [],
1456
+ messageInput: '',
1457
+ });
1458
+ threadInstance?.upsertReplyLocally?.({ message: messagePreview });
1459
+ optimisticallyUpdatedNewMessages.add(messagePreview.id);
1414
1460
 
1415
- updateMessage(messagePreview, {
1416
- commands: [],
1417
- messageInput: '',
1418
- });
1419
- threadInstance?.upsertReplyLocally?.({ message: messagePreview });
1461
+ if (enableOfflineSupport) {
1462
+ // While sending a message, we add the message to local db with failed status, so that
1463
+ // if app gets closed before message gets sent and next time user opens the app
1464
+ // then user can see that message in failed state and can retry.
1465
+ // If succesfull, it will be updated with received status.
1466
+ await dbApi.upsertMessages({
1467
+ messages: [{ ...messagePreview, cid: channel.cid, status: MessageStatusTypes.FAILED }],
1468
+ });
1469
+ }
1420
1470
 
1421
- if (enableOfflineSupport) {
1422
- // While sending a message, we add the message to local db with failed status, so that
1423
- // if app gets closed before message gets sent and next time user opens the app
1424
- // then user can see that message in failed state and can retry.
1425
- // If succesfull, it will be updated with received status.
1426
- await dbApi.upsertMessages({
1427
- messages: [{ ...messagePreview, cid: channel.cid, status: MessageStatusTypes.FAILED }],
1428
- });
1429
- }
1471
+ await sendMessageRequest(messagePreview);
1472
+ });
1430
1473
 
1431
- await sendMessageRequest(messagePreview);
1432
- };
1474
+ const retrySendMessage: MessagesContextValue<StreamChatGenerics>['retrySendMessage'] =
1475
+ useStableCallback(async (message) => {
1476
+ const statusPendingMessage = {
1477
+ ...message,
1478
+ status: MessageStatusTypes.SENDING,
1479
+ };
1433
1480
 
1434
- const retrySendMessage: MessagesContextValue<StreamChatGenerics>['retrySendMessage'] = async (
1435
- message,
1436
- ) => {
1437
- const statusPendingMessage = {
1438
- ...message,
1439
- status: MessageStatusTypes.SENDING,
1440
- };
1481
+ const messageWithoutReservedFields = removeReservedFields(statusPendingMessage);
1441
1482
 
1442
- const messageWithoutReservedFields = removeReservedFields(statusPendingMessage);
1483
+ // For bounced messages, we don't need to update the message, instead always send a new message.
1484
+ if (!isBouncedMessage(message)) {
1485
+ updateMessage(messageWithoutReservedFields as MessageResponse<StreamChatGenerics>);
1486
+ }
1443
1487
 
1444
- // For bounced messages, we don't need to update the message, instead always send a new message.
1445
- if (!isBouncedMessage(message)) {
1446
- updateMessage(messageWithoutReservedFields as MessageResponse<StreamChatGenerics>);
1447
- }
1488
+ await sendMessageRequest(
1489
+ messageWithoutReservedFields as MessageResponse<StreamChatGenerics>,
1490
+ true,
1491
+ );
1492
+ });
1448
1493
 
1449
- await sendMessageRequest(
1450
- messageWithoutReservedFields as MessageResponse<StreamChatGenerics>,
1451
- true,
1494
+ const editMessage: InputMessageInputContextValue<StreamChatGenerics>['editMessage'] =
1495
+ useStableCallback((updatedMessage) =>
1496
+ doUpdateMessageRequest
1497
+ ? doUpdateMessageRequest(channel?.cid || '', updatedMessage)
1498
+ : client.updateMessage(updatedMessage),
1452
1499
  );
1453
- };
1454
1500
 
1455
- const editMessage: InputMessageInputContextValue<StreamChatGenerics>['editMessage'] = (
1456
- updatedMessage,
1457
- ) =>
1458
- doUpdateMessageRequest
1459
- ? doUpdateMessageRequest(channel?.cid || '', updatedMessage)
1460
- : client.updateMessage(updatedMessage);
1461
-
1462
- const setEditingState: MessagesContextValue<StreamChatGenerics>['setEditingState'] = (
1463
- message,
1464
- ) => {
1465
- clearQuotedMessageState();
1466
- setEditing(message);
1467
- };
1501
+ const setEditingState: MessagesContextValue<StreamChatGenerics>['setEditingState'] =
1502
+ useStableCallback((message) => {
1503
+ clearQuotedMessageState();
1504
+ setEditing(message);
1505
+ });
1468
1506
 
1469
- const setQuotedMessageState: MessagesContextValue<StreamChatGenerics>['setQuotedMessageState'] = (
1470
- messageOrBoolean,
1471
- ) => {
1472
- setQuotedMessage(messageOrBoolean);
1473
- };
1507
+ const setQuotedMessageState: MessagesContextValue<StreamChatGenerics>['setQuotedMessageState'] =
1508
+ useStableCallback((messageOrBoolean) => {
1509
+ setQuotedMessage(messageOrBoolean);
1510
+ });
1474
1511
 
1475
1512
  const clearEditingState: InputMessageInputContextValue<StreamChatGenerics>['clearEditingState'] =
1476
- () => setEditing(undefined);
1513
+ useStableCallback(() => setEditing(undefined));
1477
1514
 
1478
1515
  const clearQuotedMessageState: InputMessageInputContextValue<StreamChatGenerics>['clearQuotedMessageState'] =
1479
- () => setQuotedMessage(undefined);
1516
+ useStableCallback(() => setQuotedMessage(undefined));
1480
1517
 
1481
1518
  /**
1482
1519
  * Removes the message from local state
1483
1520
  */
1484
- const removeMessage: MessagesContextValue<StreamChatGenerics>['removeMessage'] = async (
1485
- message,
1486
- ) => {
1487
- if (channel) {
1488
- channel.state.removeMessage(message);
1489
- copyMessagesStateFromChannel(channel);
1521
+ const removeMessage: MessagesContextValue<StreamChatGenerics>['removeMessage'] =
1522
+ useStableCallback(async (message) => {
1523
+ if (channel) {
1524
+ channel.state.removeMessage(message);
1525
+ copyMessagesStateFromChannel(channel);
1490
1526
 
1491
- if (thread) {
1492
- setThreadMessages(channel.state.threads[thread.id] || []);
1527
+ if (thread) {
1528
+ setThreadMessages(channel.state.threads[thread.id] || []);
1529
+ }
1493
1530
  }
1494
- }
1495
1531
 
1496
- if (enableOfflineSupport) {
1497
- await dbApi.deleteMessage({
1498
- id: message.id,
1499
- });
1500
- }
1501
- };
1532
+ if (enableOfflineSupport) {
1533
+ await dbApi.deleteMessage({
1534
+ id: message.id,
1535
+ });
1536
+ }
1537
+ });
1502
1538
 
1503
- const sendReaction = async (type: string, messageId: string) => {
1539
+ const sendReaction = useStableCallback(async (type: string, messageId: string) => {
1504
1540
  if (!channel?.id || !client.user) {
1505
1541
  throw new Error('Channel has not been initialized');
1506
1542
  }
@@ -1541,90 +1577,87 @@ const ChannelWithContext = <
1541
1577
  if (sendReactionResponse?.message) {
1542
1578
  threadInstance?.upsertReplyLocally?.({ message: sendReactionResponse.message });
1543
1579
  }
1544
- };
1580
+ });
1545
1581
 
1546
- const deleteMessage: MessagesContextValue<StreamChatGenerics>['deleteMessage'] = async (
1547
- message,
1548
- ) => {
1549
- if (!channel.id) {
1550
- throw new Error('Channel has not been initialized yet');
1551
- }
1582
+ const deleteMessage: MessagesContextValue<StreamChatGenerics>['deleteMessage'] =
1583
+ useStableCallback(async (message) => {
1584
+ if (!channel.id) {
1585
+ throw new Error('Channel has not been initialized yet');
1586
+ }
1587
+
1588
+ if (!enableOfflineSupport) {
1589
+ if (message.status === MessageStatusTypes.FAILED) {
1590
+ await removeMessage(message);
1591
+ return;
1592
+ }
1593
+ await client.deleteMessage(message.id);
1594
+ return;
1595
+ }
1552
1596
 
1553
- if (!enableOfflineSupport) {
1554
1597
  if (message.status === MessageStatusTypes.FAILED) {
1598
+ await DBSyncManager.dropPendingTasks({ messageId: message.id });
1555
1599
  await removeMessage(message);
1600
+ } else {
1601
+ const updatedMessage = {
1602
+ ...message,
1603
+ cid: channel.cid,
1604
+ deleted_at: new Date().toISOString(),
1605
+ type: 'deleted',
1606
+ };
1607
+ updateMessage(updatedMessage);
1608
+
1609
+ threadInstance?.upsertReplyLocally({ message: updatedMessage });
1610
+
1611
+ const data = await DBSyncManager.queueTask<StreamChatGenerics>({
1612
+ client,
1613
+ task: {
1614
+ channelId: channel.id,
1615
+ channelType: channel.type,
1616
+ messageId: message.id,
1617
+ payload: [message.id],
1618
+ type: 'delete-message',
1619
+ },
1620
+ });
1621
+
1622
+ if (data?.message) {
1623
+ updateMessage({ ...data.message });
1624
+ }
1625
+ }
1626
+ });
1627
+
1628
+ const deleteReaction: MessagesContextValue<StreamChatGenerics>['deleteReaction'] =
1629
+ useStableCallback(async (type: string, messageId: string) => {
1630
+ if (!channel?.id || !client.user) {
1631
+ throw new Error('Channel has not been initialized');
1632
+ }
1633
+
1634
+ const payload: Parameters<ChannelClass['deleteReaction']> = [messageId, type];
1635
+
1636
+ if (!enableOfflineSupport) {
1637
+ await channel.deleteReaction(...payload);
1556
1638
  return;
1557
1639
  }
1558
- await client.deleteMessage(message.id);
1559
- return;
1560
- }
1561
1640
 
1562
- if (message.status === MessageStatusTypes.FAILED) {
1563
- await DBSyncManager.dropPendingTasks({ messageId: message.id });
1564
- await removeMessage(message);
1565
- } else {
1566
- const updatedMessage = {
1567
- ...message,
1568
- cid: channel.cid,
1569
- deleted_at: new Date().toISOString(),
1570
- type: 'deleted',
1571
- };
1572
- updateMessage(updatedMessage);
1641
+ removeReactionFromLocalState({
1642
+ channel,
1643
+ messageId,
1644
+ reactionType: type,
1645
+ user: client.user,
1646
+ });
1573
1647
 
1574
- threadInstance?.upsertReplyLocally({ message: updatedMessage });
1648
+ copyMessagesStateFromChannel(channel);
1575
1649
 
1576
- const data = await DBSyncManager.queueTask<StreamChatGenerics>({
1650
+ await DBSyncManager.queueTask<StreamChatGenerics>({
1577
1651
  client,
1578
1652
  task: {
1579
1653
  channelId: channel.id,
1580
1654
  channelType: channel.type,
1581
- messageId: message.id,
1582
- payload: [message.id],
1583
- type: 'delete-message',
1655
+ messageId,
1656
+ payload,
1657
+ type: 'delete-reaction',
1584
1658
  },
1585
1659
  });
1586
-
1587
- if (data?.message) {
1588
- updateMessage({ ...data.message });
1589
- }
1590
- }
1591
- };
1592
-
1593
- const deleteReaction: MessagesContextValue<StreamChatGenerics>['deleteReaction'] = async (
1594
- type: string,
1595
- messageId: string,
1596
- ) => {
1597
- if (!channel?.id || !client.user) {
1598
- throw new Error('Channel has not been initialized');
1599
- }
1600
-
1601
- const payload: Parameters<ChannelClass['deleteReaction']> = [messageId, type];
1602
-
1603
- if (!enableOfflineSupport) {
1604
- await channel.deleteReaction(...payload);
1605
- return;
1606
- }
1607
-
1608
- removeReactionFromLocalState({
1609
- channel,
1610
- messageId,
1611
- reactionType: type,
1612
- user: client.user,
1613
- });
1614
-
1615
- copyMessagesStateFromChannel(channel);
1616
-
1617
- await DBSyncManager.queueTask<StreamChatGenerics>({
1618
- client,
1619
- task: {
1620
- channelId: channel.id,
1621
- channelType: channel.type,
1622
- messageId,
1623
- payload,
1624
- type: 'delete-reaction',
1625
- },
1626
1660
  });
1627
- };
1628
1661
 
1629
1662
  /**
1630
1663
  * THREAD METHODS
@@ -1666,46 +1699,47 @@ const ChannelWithContext = <
1666
1699
  ),
1667
1700
  ).current;
1668
1701
 
1669
- const loadMoreThread: ThreadContextValue<StreamChatGenerics>['loadMoreThread'] = async () => {
1670
- if (threadLoadingMore || !thread?.id) {
1671
- return;
1672
- }
1673
- setThreadLoadingMore(true);
1702
+ const loadMoreThread: ThreadContextValue<StreamChatGenerics>['loadMoreThread'] =
1703
+ useStableCallback(async () => {
1704
+ if (threadLoadingMore || !thread?.id) {
1705
+ return;
1706
+ }
1707
+ setThreadLoadingMore(true);
1674
1708
 
1675
- try {
1676
- if (channel) {
1677
- const parentID = thread.id;
1678
-
1679
- /**
1680
- * In the channel is re-initializing, then threads may get wiped out during the process
1681
- * (check `addMessagesSorted` method on channel.state). In those cases, we still want to
1682
- * preserve the messages on active thread, so lets simply copy messages from UI state to
1683
- * `channel.state`.
1684
- */
1685
- channel.state.threads[parentID] = threadMessages;
1686
- const oldestMessageID = threadMessages?.[0]?.id;
1687
-
1688
- const limit = 50;
1689
- const queryResponse = await channel.getReplies(parentID, {
1690
- id_lt: oldestMessageID,
1691
- limit,
1692
- });
1709
+ try {
1710
+ if (channel) {
1711
+ const parentID = thread.id;
1712
+
1713
+ /**
1714
+ * In the channel is re-initializing, then threads may get wiped out during the process
1715
+ * (check `addMessagesSorted` method on channel.state). In those cases, we still want to
1716
+ * preserve the messages on active thread, so lets simply copy messages from UI state to
1717
+ * `channel.state`.
1718
+ */
1719
+ channel.state.threads[parentID] = threadMessages;
1720
+ const oldestMessageID = threadMessages?.[0]?.id;
1721
+
1722
+ const limit = 50;
1723
+ const queryResponse = await channel.getReplies(parentID, {
1724
+ id_lt: oldestMessageID,
1725
+ limit,
1726
+ });
1693
1727
 
1694
- const updatedHasMore = queryResponse.messages.length === limit;
1695
- const updatedThreadMessages = channel.state.threads[parentID] || [];
1696
- loadMoreThreadFinished(updatedHasMore, updatedThreadMessages);
1697
- }
1698
- } catch (err) {
1699
- console.warn('Message pagination request failed with error', err);
1700
- if (err instanceof Error) {
1701
- setError(err);
1702
- } else {
1703
- setError(true);
1728
+ const updatedHasMore = queryResponse.messages.length === limit;
1729
+ const updatedThreadMessages = channel.state.threads[parentID] || [];
1730
+ loadMoreThreadFinished(updatedHasMore, updatedThreadMessages);
1731
+ }
1732
+ } catch (err) {
1733
+ console.warn('Message pagination request failed with error', err);
1734
+ if (err instanceof Error) {
1735
+ setError(err);
1736
+ } else {
1737
+ setError(true);
1738
+ }
1739
+ setThreadLoadingMore(false);
1740
+ throw err;
1704
1741
  }
1705
- setThreadLoadingMore(false);
1706
- throw err;
1707
- }
1708
- };
1742
+ });
1709
1743
 
1710
1744
  const ownCapabilitiesContext = useCreateOwnCapabilitiesContext({
1711
1745
  channel,
@@ -1757,14 +1791,11 @@ const ChannelWithContext = <
1757
1791
  // but it is definitely not trivial, especially considering it depends on other inline functions that
1758
1792
  // are not wrapped in a useCallback() themselves hence creating a huge cascading change. Can be removed
1759
1793
  // once our memoization issues are fixed in most places in the app or we move to a reactive state store.
1760
- const sendMessageRef =
1761
- useRef<InputMessageInputContextValue<StreamChatGenerics>['sendMessage']>(sendMessage);
1762
- sendMessageRef.current = sendMessage;
1763
- const sendMessageStable = useCallback<
1764
- InputMessageInputContextValue<StreamChatGenerics>['sendMessage']
1765
- >((...args) => {
1766
- return sendMessageRef.current(...args);
1767
- }, []);
1794
+ // const sendMessageRef = useRef<InputMessageInputContextValue['sendMessage']>(sendMessage);
1795
+ // sendMessageRef.current = sendMessage;
1796
+ // const sendMessageStable = useCallback<InputMessageInputContextValue['sendMessage']>((...args) => {
1797
+ // return sendMessageRef.current(...args);
1798
+ // }, []);
1768
1799
 
1769
1800
  const inputMessageInputContext = useCreateInputMessageInputContext<StreamChatGenerics>({
1770
1801
  additionalTextInputProps,
@@ -1818,7 +1849,7 @@ const ChannelWithContext = <
1818
1849
  quotedMessage,
1819
1850
  SendButton,
1820
1851
  sendImageAsync,
1821
- sendMessage: sendMessageStable,
1852
+ sendMessage,
1822
1853
  SendMessageDisallowedIndicator,
1823
1854
  setInputRef,
1824
1855
  setQuotedMessageState,