stream-chat-react-native-core 6.7.3-beta.2 → 6.7.3-beta.4

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 (79) hide show
  1. package/lib/commonjs/components/Attachment/AudioAttachment.js +3 -1
  2. package/lib/commonjs/components/Attachment/AudioAttachment.js.map +1 -1
  3. package/lib/commonjs/components/Channel/Channel.js +273 -281
  4. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  5. package/lib/commonjs/components/Channel/hooks/useMessageListPagination.js +133 -147
  6. package/lib/commonjs/components/Channel/hooks/useMessageListPagination.js.map +1 -1
  7. package/lib/commonjs/components/KeyboardCompatibleView/KeyboardCompatibleView.js +7 -12
  8. package/lib/commonjs/components/KeyboardCompatibleView/KeyboardCompatibleView.js.map +1 -1
  9. package/lib/commonjs/components/MessageList/MessageList.js +167 -179
  10. package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
  11. package/lib/commonjs/components/MessageList/hooks/useMessageList.js +60 -37
  12. package/lib/commonjs/components/MessageList/hooks/useMessageList.js.map +1 -1
  13. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +450 -459
  14. package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  15. package/lib/commonjs/contexts/messagesContext/MessagesContext.js.map +1 -1
  16. package/lib/commonjs/hooks/index.js +11 -0
  17. package/lib/commonjs/hooks/index.js.map +1 -1
  18. package/lib/commonjs/hooks/useAudioPlayer.js +1 -1
  19. package/lib/commonjs/hooks/useAudioPlayer.js.map +1 -1
  20. package/lib/commonjs/hooks/useStableCallback.js +13 -0
  21. package/lib/commonjs/hooks/useStableCallback.js.map +1 -0
  22. package/lib/commonjs/native.js.map +1 -1
  23. package/lib/commonjs/version.json +1 -1
  24. package/lib/module/components/Attachment/AudioAttachment.js +3 -1
  25. package/lib/module/components/Attachment/AudioAttachment.js.map +1 -1
  26. package/lib/module/components/Channel/Channel.js +273 -281
  27. package/lib/module/components/Channel/Channel.js.map +1 -1
  28. package/lib/module/components/Channel/hooks/useMessageListPagination.js +133 -147
  29. package/lib/module/components/Channel/hooks/useMessageListPagination.js.map +1 -1
  30. package/lib/module/components/KeyboardCompatibleView/KeyboardCompatibleView.js +7 -12
  31. package/lib/module/components/KeyboardCompatibleView/KeyboardCompatibleView.js.map +1 -1
  32. package/lib/module/components/MessageList/MessageList.js +167 -179
  33. package/lib/module/components/MessageList/MessageList.js.map +1 -1
  34. package/lib/module/components/MessageList/hooks/useMessageList.js +60 -37
  35. package/lib/module/components/MessageList/hooks/useMessageList.js.map +1 -1
  36. package/lib/module/contexts/messageInputContext/MessageInputContext.js +450 -459
  37. package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
  38. package/lib/module/contexts/messagesContext/MessagesContext.js.map +1 -1
  39. package/lib/module/hooks/index.js +11 -0
  40. package/lib/module/hooks/index.js.map +1 -1
  41. package/lib/module/hooks/useAudioPlayer.js +1 -1
  42. package/lib/module/hooks/useAudioPlayer.js.map +1 -1
  43. package/lib/module/hooks/useStableCallback.js +13 -0
  44. package/lib/module/hooks/useStableCallback.js.map +1 -0
  45. package/lib/module/native.js.map +1 -1
  46. package/lib/module/version.json +1 -1
  47. package/lib/typescript/components/Attachment/AudioAttachment.d.ts.map +1 -1
  48. package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
  49. package/lib/typescript/components/Channel/hooks/useMessageListPagination.d.ts +3 -3
  50. package/lib/typescript/components/Channel/hooks/useMessageListPagination.d.ts.map +1 -1
  51. package/lib/typescript/components/KeyboardCompatibleView/KeyboardCompatibleView.d.ts +3 -0
  52. package/lib/typescript/components/KeyboardCompatibleView/KeyboardCompatibleView.d.ts.map +1 -1
  53. package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
  54. package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts +4 -0
  55. package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts.map +1 -1
  56. package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts.map +1 -1
  57. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts +1 -1
  58. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts.map +1 -1
  59. package/lib/typescript/hooks/index.d.ts +1 -0
  60. package/lib/typescript/hooks/index.d.ts.map +1 -1
  61. package/lib/typescript/hooks/useStableCallback.d.ts +26 -0
  62. package/lib/typescript/hooks/useStableCallback.d.ts.map +1 -0
  63. package/lib/typescript/native.d.ts +3 -1
  64. package/lib/typescript/native.d.ts.map +1 -1
  65. package/package.json +1 -1
  66. package/src/components/Attachment/AudioAttachment.tsx +2 -0
  67. package/src/components/Channel/Channel.tsx +424 -408
  68. package/src/components/Channel/hooks/useMessageListPagination.tsx +152 -147
  69. package/src/components/KeyboardCompatibleView/KeyboardCompatibleView.tsx +6 -4
  70. package/src/components/MessageList/MessageList.tsx +147 -112
  71. package/src/components/MessageList/hooks/useMessageList.ts +69 -38
  72. package/src/contexts/messageInputContext/MessageInputContext.tsx +293 -267
  73. package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx +2 -1
  74. package/src/contexts/messagesContext/MessagesContext.tsx +1 -0
  75. package/src/hooks/index.ts +1 -0
  76. package/src/hooks/useAudioPlayer.ts +1 -1
  77. package/src/hooks/useStableCallback.ts +37 -0
  78. package/src/native.ts +8 -1
  79. package/src/version.json +1 -1
@@ -4,6 +4,7 @@ import React, {
4
4
  useCallback,
5
5
  useContext,
6
6
  useEffect,
7
+ useMemo,
7
8
  useRef,
8
9
  useState,
9
10
  } from 'react';
@@ -610,19 +611,19 @@ export const MessageInputProvider = <
610
611
  text,
611
612
  } = useMessageDetailsForState<StreamChatGenerics>(editing, initialValue);
612
613
  const { endsAt: cooldownEndsAt, start: startCooldown } = useCooldown<StreamChatGenerics>();
613
- const { onChangeText } = value;
614
+ const { onChangeText, emojiSearchIndex, autoCompleteTriggerSettings } = value;
614
615
 
615
616
  const threadId = thread?.id;
616
617
  useEffect(() => {
617
618
  setSendThreadMessageInChannel(false);
618
619
  }, [threadId]);
619
620
 
620
- const appendText = (newText: string) => {
621
+ const appendText = useStableCallback((newText: string) => {
621
622
  setText((prevText) => `${prevText}${newText}`);
622
- };
623
+ });
623
624
 
624
625
  /** Checks if the message is valid or not. Accordingly we can enable/disable send button */
625
- const isValidMessage = () => {
626
+ const isValidMessage = useStableCallback(() => {
626
627
  if (text && text.trim()) {
627
628
  return true;
628
629
  }
@@ -656,7 +657,7 @@ export const MessageInputProvider = <
656
657
  }
657
658
 
658
659
  return false;
659
- };
660
+ });
660
661
 
661
662
  const onChange = useCallback(
662
663
  (newText: string) => {
@@ -676,24 +677,24 @@ export const MessageInputProvider = <
676
677
  [channel, channelCapabities.sendTypingEvents, isOnline, setText, thread?.id, onChangeText],
677
678
  );
678
679
 
679
- const openCommandsPicker = () => {
680
+ const openCommandsPicker = useStableCallback(() => {
680
681
  appendText('/');
681
682
  if (inputBoxRef.current) {
682
683
  inputBoxRef.current.focus();
683
684
  }
684
- };
685
+ });
685
686
 
686
- const openMentionsPicker = () => {
687
+ const openMentionsPicker = useStableCallback(() => {
687
688
  appendText('@');
688
689
  if (inputBoxRef.current) {
689
690
  inputBoxRef.current.focus();
690
691
  }
691
- };
692
+ });
692
693
 
693
694
  /**
694
695
  * Function for capturing a photo and uploading it
695
696
  */
696
- const takeAndUploadImage = async (mediaType?: MediaTypes) => {
697
+ const takeAndUploadImage = useStableCallback(async (mediaType?: MediaTypes) => {
697
698
  setSelectedPicker(undefined);
698
699
  closePicker();
699
700
  const photo = await NativeHandlers.takePhoto({
@@ -717,12 +718,12 @@ export const MessageInputProvider = <
717
718
  await uploadNewFile({ ...photo, mimeType: photo.type, type: FileTypes.Video });
718
719
  }
719
720
  }
720
- };
721
+ });
721
722
 
722
723
  /**
723
724
  * Function for picking a photo from native image picker and uploading it
724
725
  */
725
- const pickAndUploadImageFromNativePicker = async () => {
726
+ const pickAndUploadImageFromNativePicker = useStableCallback(async () => {
726
727
  const result = await NativeHandlers.pickImage();
727
728
  if (result.askToOpenSettings) {
728
729
  Alert.alert(
@@ -755,7 +756,7 @@ export const MessageInputProvider = <
755
756
  }
756
757
  });
757
758
  }
758
- };
759
+ });
759
760
 
760
761
  /**
761
762
  * Function to open the attachment picker if the MediaLibary is installed.
@@ -785,11 +786,11 @@ export const MessageInputProvider = <
785
786
  }
786
787
  }, [closeAttachmentPicker, openAttachmentPicker, selectedPicker]);
787
788
 
788
- const onSelectItem = (item: UserResponse<StreamChatGenerics>) => {
789
+ const onSelectItem = useStableCallback((item: UserResponse<StreamChatGenerics>) => {
789
790
  setMentionedUsers((prevMentionedUsers) => [...prevMentionedUsers, item.id]);
790
- };
791
+ });
791
792
 
792
- const pickFile = async () => {
793
+ const pickFile = useStableCallback(async () => {
793
794
  if (!isDocumentPickerAvailable()) {
794
795
  console.log(
795
796
  'The file picker is not installed. Check our Getting Started documentation to install it.',
@@ -818,7 +819,7 @@ export const MessageInputProvider = <
818
819
  await uploadNewFile(asset);
819
820
  });
820
821
  }
821
- };
822
+ });
822
823
 
823
824
  const removeFile = useCallback(
824
825
  (id: string) => {
@@ -840,242 +841,257 @@ export const MessageInputProvider = <
840
841
  [imageUploads, setImageUploads, setNumberOfUploads],
841
842
  );
842
843
 
843
- const resetInput = (pendingAttachments: Attachment<StreamChatGenerics>[] = []) => {
844
- /**
845
- * If the MediaLibrary is available, reset the selected files and images
846
- */
847
- if (isImageMediaLibraryAvailable()) {
848
- setSelectedFiles([]);
849
- setSelectedImages([]);
850
- }
851
-
852
- setFileUploads([]);
853
- setGiphyActive(false);
854
- setShowMoreOptions(true);
855
- setImageUploads([]);
856
- setMentionedUsers([]);
857
- setNumberOfUploads(
858
- (prevNumberOfUploads) => prevNumberOfUploads - (pendingAttachments?.length || 0),
859
- );
860
- setText('');
861
- if (value.editing) {
862
- value.clearEditingState();
863
- }
864
- };
844
+ const resetInput = useStableCallback(
845
+ (pendingAttachments: Attachment<StreamChatGenerics>[] = []) => {
846
+ /**
847
+ * If the MediaLibrary is available, reset the selected files and images
848
+ */
849
+ if (isImageMediaLibraryAvailable()) {
850
+ setSelectedFiles([]);
851
+ setSelectedImages([]);
852
+ }
865
853
 
866
- const mapImageUploadToAttachment = (image: ImageUpload): Attachment<StreamChatGenerics> => {
867
- const mime_type: string | boolean = lookup(image.file.name as string);
868
- const name = image.file.name as string;
869
- return {
870
- fallback: name,
871
- image_url: image.url,
872
- mime_type: mime_type ? mime_type : undefined,
873
- original_height: image.height,
874
- original_width: image.width,
875
- originalImage: image.file,
876
- type: FileTypes.Image,
877
- };
878
- };
854
+ setFileUploads([]);
855
+ setGiphyActive(false);
856
+ setShowMoreOptions(true);
857
+ setImageUploads([]);
858
+ setMentionedUsers([]);
859
+ setNumberOfUploads(
860
+ (prevNumberOfUploads) => prevNumberOfUploads - (pendingAttachments?.length || 0),
861
+ );
862
+ setText('');
863
+ if (value.editing) {
864
+ value.clearEditingState();
865
+ }
866
+ },
867
+ );
879
868
 
880
- const mapFileUploadToAttachment = (file: FileUpload): Attachment<StreamChatGenerics> => {
881
- if (file.type === FileTypes.Image) {
869
+ const mapImageUploadToAttachment = useStableCallback(
870
+ (image: ImageUpload): Attachment<StreamChatGenerics> => {
871
+ const mime_type: string | boolean = lookup(image.file.name as string);
872
+ const name = image.file.name as string;
882
873
  return {
883
- fallback: file.file.name,
884
- image_url: file.url,
885
- mime_type: file.file.mimeType,
886
- originalFile: file.file,
874
+ fallback: name,
875
+ image_url: image.url,
876
+ mime_type: mime_type ? mime_type : undefined,
877
+ original_height: image.height,
878
+ original_width: image.width,
879
+ originalImage: image.file,
887
880
  type: FileTypes.Image,
888
881
  };
889
- } else if (file.type === FileTypes.Audio) {
890
- return {
891
- asset_url: file.url || file.file.uri,
892
- duration: file.file.duration,
893
- file_size: file.file.size,
894
- mime_type: file.file.mimeType,
895
- originalFile: file.file,
896
- title: file.file.name,
897
- type: FileTypes.Audio,
898
- };
899
- } else if (file.type === FileTypes.Video) {
900
- return {
901
- asset_url: file.url || file.file.uri,
902
- duration: file.file.duration,
903
- file_size: file.file.size,
904
- mime_type: file.file.mimeType,
905
- originalFile: file.file,
906
- thumb_url: file.thumb_url,
907
- title: file.file.name,
908
- type: FileTypes.Video,
909
- };
910
- } else if (file.type === FileTypes.VoiceRecording) {
911
- return {
912
- asset_url: file.url || file.file.uri,
913
- duration: file.file.duration,
914
- file_size: file.file.size,
915
- mime_type: file.file.mimeType,
916
- originalFile: file.file,
917
- title: file.file.name,
918
- type: FileTypes.VoiceRecording,
919
- waveform_data: file.file.waveform_data,
920
- };
921
- } else {
922
- return {
923
- asset_url: file.url || file.file.uri,
924
- file_size: file.file.size,
925
- mime_type: file.file.mimeType,
926
- originalFile: file.file,
927
- title: file.file.name,
928
- type: FileTypes.File,
929
- };
930
- }
931
- };
882
+ },
883
+ );
884
+
885
+ const mapFileUploadToAttachment = useStableCallback(
886
+ (file: FileUpload): Attachment<StreamChatGenerics> => {
887
+ if (file.type === FileTypes.Image) {
888
+ return {
889
+ fallback: file.file.name,
890
+ image_url: file.url,
891
+ mime_type: file.file.mimeType,
892
+ originalFile: file.file,
893
+ type: FileTypes.Image,
894
+ };
895
+ } else if (file.type === FileTypes.Audio) {
896
+ return {
897
+ asset_url: file.url || file.file.uri,
898
+ duration: file.file.duration,
899
+ file_size: file.file.size,
900
+ mime_type: file.file.mimeType,
901
+ originalFile: file.file,
902
+ title: file.file.name,
903
+ type: FileTypes.Audio,
904
+ };
905
+ } else if (file.type === FileTypes.Video) {
906
+ return {
907
+ asset_url: file.url || file.file.uri,
908
+ duration: file.file.duration,
909
+ file_size: file.file.size,
910
+ mime_type: file.file.mimeType,
911
+ originalFile: file.file,
912
+ thumb_url: file.thumb_url,
913
+ title: file.file.name,
914
+ type: FileTypes.Video,
915
+ };
916
+ } else if (file.type === FileTypes.VoiceRecording) {
917
+ return {
918
+ asset_url: file.url || file.file.uri,
919
+ duration: file.file.duration,
920
+ file_size: file.file.size,
921
+ mime_type: file.file.mimeType,
922
+ originalFile: file.file,
923
+ title: file.file.name,
924
+ type: FileTypes.VoiceRecording,
925
+ waveform_data: file.file.waveform_data,
926
+ };
927
+ } else {
928
+ return {
929
+ asset_url: file.url || file.file.uri,
930
+ file_size: file.file.size,
931
+ mime_type: file.file.mimeType,
932
+ originalFile: file.file,
933
+ title: file.file.name,
934
+ type: FileTypes.File,
935
+ };
936
+ }
937
+ },
938
+ );
932
939
 
933
940
  // TODO: Figure out why this is async, as it doesn't await any promise.
934
- const sendMessage = async ({
935
- customMessageData,
936
- }: {
937
- customMessageData?: Partial<Message<StreamChatGenerics>>;
938
- } = {}) => {
939
- if (sending.current) {
940
- return;
941
- }
942
- const linkInfos = parseLinksFromText(text);
941
+ const sendMessage = useStableCallback(
942
+ async ({
943
+ customMessageData,
944
+ }: {
945
+ customMessageData?: Partial<Message<StreamChatGenerics>>;
946
+ } = {}) => {
947
+ if (sending.current) {
948
+ return;
949
+ }
950
+ const linkInfos = parseLinksFromText(text);
943
951
 
944
- if (!channelCapabities.sendLinks && linkInfos.length > 0) {
945
- Alert.alert(t('Links are disabled'), t('Sending links is not allowed in this conversation'));
952
+ if (!channelCapabities.sendLinks && linkInfos.length > 0) {
953
+ Alert.alert(
954
+ t('Links are disabled'),
955
+ t('Sending links is not allowed in this conversation'),
956
+ );
946
957
 
947
- return;
948
- }
958
+ return;
959
+ }
949
960
 
950
- sending.current = true;
961
+ sending.current = true;
951
962
 
952
- startCooldown();
963
+ startCooldown();
953
964
 
954
- const prevText = giphyEnabled && giphyActive ? `/giphy ${text}` : text;
955
- setText('');
965
+ const prevText = giphyEnabled && giphyActive ? `/giphy ${text}` : text;
966
+ setText('');
956
967
 
957
- const attachments = [] as Attachment<StreamChatGenerics>[];
958
- for (const image of imageUploads) {
959
- if (enableOfflineSupport) {
960
- if (image.state === FileState.NOT_SUPPORTED) {
961
- return;
962
- }
963
- attachments.push(mapImageUploadToAttachment(image));
964
- continue;
968
+ if (inputBoxRef.current) {
969
+ inputBoxRef.current.clear();
965
970
  }
966
971
 
967
- if ((!image || image.state === FileState.UPLOAD_FAILED) && !enableOfflineSupport) {
968
- continue;
969
- }
972
+ const attachments = [] as Attachment<StreamChatGenerics>[];
973
+ for (const image of imageUploads) {
974
+ if (enableOfflineSupport) {
975
+ if (image.state === FileState.NOT_SUPPORTED) {
976
+ return;
977
+ }
978
+ attachments.push(mapImageUploadToAttachment(image));
979
+ continue;
980
+ }
970
981
 
971
- if (image.state === FileState.UPLOADING) {
972
- // TODO: show error to user that they should wait until image is uploaded
973
- if (value.sendImageAsync) {
974
- /**
975
- * If user hit send before image uploaded, push ID into a queue to later
976
- * be matched with the successful CDN response
977
- */
978
- setAsyncIds((prevAsyncIds) => [...prevAsyncIds, image.id]);
979
- } else {
980
- sending.current = false;
981
- return setText(prevText);
982
+ if ((!image || image.state === FileState.UPLOAD_FAILED) && !enableOfflineSupport) {
983
+ continue;
982
984
  }
983
- }
984
985
 
985
- // To get the mime type of the image from the file name and send it as an response for an image
986
- if (image.state === FileState.UPLOADED || image.state === FileState.FINISHED) {
987
- attachments.push(mapImageUploadToAttachment(image));
986
+ if (image.state === FileState.UPLOADING) {
987
+ // TODO: show error to user that they should wait until image is uploaded
988
+ if (value.sendImageAsync) {
989
+ /**
990
+ * If user hit send before image uploaded, push ID into a queue to later
991
+ * be matched with the successful CDN response
992
+ */
993
+ setAsyncIds((prevAsyncIds) => [...prevAsyncIds, image.id]);
994
+ } else {
995
+ sending.current = false;
996
+ return setText(prevText);
997
+ }
998
+ }
999
+
1000
+ // To get the mime type of the image from the file name and send it as an response for an image
1001
+ if (image.state === FileState.UPLOADED || image.state === FileState.FINISHED) {
1002
+ attachments.push(mapImageUploadToAttachment(image));
1003
+ }
988
1004
  }
989
- }
990
1005
 
991
- for (const file of fileUploads) {
992
- if (enableOfflineSupport) {
993
- if (file.state === FileState.NOT_SUPPORTED) {
1006
+ for (const file of fileUploads) {
1007
+ if (enableOfflineSupport) {
1008
+ if (file.state === FileState.NOT_SUPPORTED) {
1009
+ return;
1010
+ }
1011
+ attachments.push(mapFileUploadToAttachment(file));
1012
+ continue;
1013
+ }
1014
+
1015
+ if (!file || file.state === FileState.UPLOAD_FAILED) {
1016
+ continue;
1017
+ }
1018
+
1019
+ if (file.state === FileState.UPLOADING) {
1020
+ // TODO: show error to user that they should wait until image is uploaded
1021
+ sending.current = false;
994
1022
  return;
995
1023
  }
996
- attachments.push(mapFileUploadToAttachment(file));
997
- continue;
998
- }
999
1024
 
1000
- if (!file || file.state === FileState.UPLOAD_FAILED) {
1001
- continue;
1025
+ if (file.state === FileState.UPLOADED || file.state === FileState.FINISHED) {
1026
+ attachments.push(mapFileUploadToAttachment(file));
1027
+ }
1002
1028
  }
1003
1029
 
1004
- if (file.state === FileState.UPLOADING) {
1005
- // TODO: show error to user that they should wait until image is uploaded
1030
+ // Disallow sending message if its empty.
1031
+ if (!prevText && attachments.length === 0 && !customMessageData?.poll_id) {
1006
1032
  sending.current = false;
1007
1033
  return;
1008
1034
  }
1009
1035
 
1010
- if (file.state === FileState.UPLOADED || file.state === FileState.FINISHED) {
1011
- attachments.push(mapFileUploadToAttachment(file));
1012
- }
1013
- }
1014
-
1015
- // Disallow sending message if its empty.
1016
- if (!prevText && attachments.length === 0 && !customMessageData?.poll_id) {
1017
- sending.current = false;
1018
- return;
1019
- }
1020
-
1021
- const message = value.editing;
1022
- if (message && message.type !== 'error') {
1023
- const updatedMessage = {
1024
- ...message,
1025
- attachments,
1026
- mentioned_users: mentionedUsers,
1027
- quoted_message: undefined,
1028
- text: prevText,
1029
- ...customMessageData,
1030
- } as Parameters<StreamChat<StreamChatGenerics>['updateMessage']>[0];
1031
-
1032
- // TODO: Remove this line and show an error when submit fails
1033
- value.clearEditingState();
1034
-
1035
- const updateMessagePromise = value
1036
- .editMessage(
1037
- // @ts-ignore
1038
- removeReservedFields(updatedMessage),
1039
- )
1040
- .then(value.clearEditingState);
1041
- resetInput(attachments);
1042
- logChatPromiseExecution(updateMessagePromise, 'update message');
1043
-
1044
- sending.current = false;
1045
- } else {
1046
- try {
1047
- /**
1048
- * If the message is bounced by moderation, we firstly remove the message from message list and then send a new message.
1049
- */
1050
- if (message && isBouncedMessage(message as MessageType<StreamChatGenerics>)) {
1051
- await removeMessage(message);
1052
- }
1053
- value.sendMessage({
1036
+ const message = value.editing;
1037
+ if (message && message.type !== 'error') {
1038
+ const updatedMessage = {
1039
+ ...message,
1054
1040
  attachments,
1055
- mentioned_users: uniq(mentionedUsers),
1056
- /** Parent message id - in case of thread */
1057
- parent_id: thread?.id,
1058
- quoted_message_id: value.quotedMessage ? value.quotedMessage.id : undefined,
1059
- show_in_channel: sendThreadMessageInChannel || undefined,
1041
+ mentioned_users: mentionedUsers,
1042
+ quoted_message: undefined,
1060
1043
  text: prevText,
1061
1044
  ...customMessageData,
1062
- } as unknown as StreamMessage<StreamChatGenerics>);
1063
-
1064
- value.clearQuotedMessageState();
1065
- sending.current = false;
1045
+ } as Parameters<StreamChat<StreamChatGenerics>['updateMessage']>[0];
1046
+
1047
+ // TODO: Remove this line and show an error when submit fails
1048
+ value.clearEditingState();
1049
+
1050
+ const updateMessagePromise = value
1051
+ .editMessage(
1052
+ // @ts-ignore
1053
+ removeReservedFields(updatedMessage),
1054
+ )
1055
+ .then(value.clearEditingState);
1056
+ logChatPromiseExecution(updateMessagePromise, 'update message');
1066
1057
  resetInput(attachments);
1067
- } catch (_error) {
1058
+
1068
1059
  sending.current = false;
1069
- if (value.quotedMessage && typeof value.quotedMessage !== 'boolean') {
1070
- value.setQuotedMessageState(value.quotedMessage);
1060
+ } else {
1061
+ try {
1062
+ /**
1063
+ * If the message is bounced by moderation, we firstly remove the message from message list and then send a new message.
1064
+ */
1065
+ if (message && isBouncedMessage(message as MessageType<StreamChatGenerics>)) {
1066
+ await removeMessage(message);
1067
+ }
1068
+ value.sendMessage({
1069
+ attachments,
1070
+ mentioned_users: uniq(mentionedUsers),
1071
+ /** Parent message id - in case of thread */
1072
+ parent_id: thread?.id,
1073
+ quoted_message_id: value.quotedMessage ? value.quotedMessage.id : undefined,
1074
+ show_in_channel: sendThreadMessageInChannel || undefined,
1075
+ text: prevText,
1076
+ ...customMessageData,
1077
+ } as unknown as StreamMessage<StreamChatGenerics>);
1078
+
1079
+ value.clearQuotedMessageState();
1080
+ sending.current = false;
1081
+ resetInput(attachments);
1082
+ } catch (_error) {
1083
+ sending.current = false;
1084
+ if (value.quotedMessage && typeof value.quotedMessage !== 'boolean') {
1085
+ value.setQuotedMessageState(value.quotedMessage);
1086
+ }
1087
+ setText(prevText.slice(giphyEnabled && giphyActive ? 7 : 0)); // 7 because of '/giphy ' length
1088
+ console.log('Failed to send message');
1071
1089
  }
1072
- setText(prevText.slice(giphyEnabled && giphyActive ? 7 : 0)); // 7 because of '/giphy ' length
1073
- console.log('Failed to send message');
1074
1090
  }
1075
- }
1076
- };
1091
+ },
1092
+ );
1077
1093
 
1078
- const sendMessageAsync = (id: string) => {
1094
+ const sendMessageAsync = useStableCallback((id: string) => {
1079
1095
  const image = asyncUploads[id];
1080
1096
  if (!image || image.state === FileState.UPLOAD_FAILED) {
1081
1097
  return;
@@ -1111,31 +1127,31 @@ export const MessageInputProvider = <
1111
1127
  console.log('Failed');
1112
1128
  }
1113
1129
  }
1114
- };
1130
+ });
1115
1131
 
1116
- const setInputBoxRef = (ref: TextInput | null) => {
1132
+ const setInputBoxRef = useStableCallback((ref: TextInput | null) => {
1117
1133
  inputBoxRef.current = ref;
1118
1134
  if (value.setInputRef) {
1119
1135
  value.setInputRef(ref);
1120
1136
  }
1121
- };
1137
+ });
1122
1138
 
1123
- const getTriggerSettings = () => {
1139
+ const triggerSettings = useMemo(() => {
1124
1140
  try {
1125
1141
  let triggerSettings: TriggerSettings<StreamChatGenerics> = {};
1126
1142
  if (channel) {
1127
- if (value.autoCompleteTriggerSettings) {
1128
- triggerSettings = value.autoCompleteTriggerSettings({
1143
+ if (autoCompleteTriggerSettings) {
1144
+ triggerSettings = autoCompleteTriggerSettings({
1129
1145
  channel,
1130
1146
  client,
1131
- emojiSearchIndex: value.emojiSearchIndex,
1147
+ emojiSearchIndex,
1132
1148
  onMentionSelectItem: onSelectItem,
1133
1149
  });
1134
1150
  } else {
1135
1151
  triggerSettings = ACITriggerSettings<StreamChatGenerics>({
1136
1152
  channel,
1137
1153
  client,
1138
- emojiSearchIndex: value.emojiSearchIndex,
1154
+ emojiSearchIndex,
1139
1155
  onMentionSelectItem: onSelectItem,
1140
1156
  });
1141
1157
  }
@@ -1145,11 +1161,11 @@ export const MessageInputProvider = <
1145
1161
  console.warn('Error in getting trigger settings', error);
1146
1162
  throw error;
1147
1163
  }
1148
- };
1164
+ }, [channel, client, onSelectItem, autoCompleteTriggerSettings, emojiSearchIndex]);
1149
1165
 
1150
- const triggerSettings = getTriggerSettings();
1166
+ // const triggerSettings = getTriggerSettings();
1151
1167
 
1152
- const updateMessage = async () => {
1168
+ const updateMessage = useStableCallback(async () => {
1153
1169
  try {
1154
1170
  if (value.editing) {
1155
1171
  await client.updateMessage({
@@ -1164,51 +1180,54 @@ export const MessageInputProvider = <
1164
1180
  } catch (error) {
1165
1181
  console.log(error);
1166
1182
  }
1167
- };
1183
+ });
1168
1184
 
1169
1185
  const regexCondition = /File (extension \.\w{2,4}|type \S+) is not supported/;
1170
1186
 
1171
- const getUploadSetStateAction =
1187
+ const getUploadSetStateAction = useStableCallback(
1172
1188
  <UploadType extends ImageUpload | FileUpload>(
1173
1189
  id: string,
1174
1190
  fileState: FileStateValue,
1175
1191
  extraData: Partial<UploadType> = {},
1176
1192
  ): React.SetStateAction<UploadType[]> =>
1177
- (prevUploads: UploadType[]) =>
1178
- prevUploads.map((prevUpload) => {
1179
- if (prevUpload.id === id) {
1180
- return {
1181
- ...prevUpload,
1182
- ...extraData,
1183
- state: fileState,
1184
- };
1185
- }
1186
- return prevUpload;
1187
- });
1193
+ (prevUploads: UploadType[]) =>
1194
+ prevUploads.map((prevUpload) => {
1195
+ if (prevUpload.id === id) {
1196
+ return {
1197
+ ...prevUpload,
1198
+ ...extraData,
1199
+ state: fileState,
1200
+ };
1201
+ }
1202
+ return prevUpload;
1203
+ }),
1204
+ );
1188
1205
 
1189
- const handleFileOrImageUploadError = (error: unknown, isImageError: boolean, id: string) => {
1190
- if (isImageError) {
1191
- setNumberOfUploads((prevNumberOfUploads) => prevNumberOfUploads - 1);
1192
- if (error instanceof Error) {
1193
- if (regexCondition.test(error.message)) {
1194
- return setImageUploads(getUploadSetStateAction(id, FileState.NOT_SUPPORTED));
1195
- }
1206
+ const handleFileOrImageUploadError = useStableCallback(
1207
+ (error: unknown, isImageError: boolean, id: string) => {
1208
+ if (isImageError) {
1209
+ setNumberOfUploads((prevNumberOfUploads) => prevNumberOfUploads - 1);
1210
+ if (error instanceof Error) {
1211
+ if (regexCondition.test(error.message)) {
1212
+ return setImageUploads(getUploadSetStateAction(id, FileState.NOT_SUPPORTED));
1213
+ }
1196
1214
 
1197
- return setImageUploads(getUploadSetStateAction(id, FileState.UPLOAD_FAILED));
1198
- }
1199
- } else {
1200
- setNumberOfUploads((prevNumberOfUploads) => prevNumberOfUploads - 1);
1215
+ return setImageUploads(getUploadSetStateAction(id, FileState.UPLOAD_FAILED));
1216
+ }
1217
+ } else {
1218
+ setNumberOfUploads((prevNumberOfUploads) => prevNumberOfUploads - 1);
1201
1219
 
1202
- if (error instanceof Error) {
1203
- if (regexCondition.test(error.message)) {
1204
- return setFileUploads(getUploadSetStateAction(id, FileState.NOT_SUPPORTED));
1220
+ if (error instanceof Error) {
1221
+ if (regexCondition.test(error.message)) {
1222
+ return setFileUploads(getUploadSetStateAction(id, FileState.NOT_SUPPORTED));
1223
+ }
1224
+ return setFileUploads(getUploadSetStateAction(id, FileState.UPLOAD_FAILED));
1205
1225
  }
1206
- return setFileUploads(getUploadSetStateAction(id, FileState.UPLOAD_FAILED));
1207
1226
  }
1208
- }
1209
- };
1227
+ },
1228
+ );
1210
1229
 
1211
- const uploadFile = async ({ newFile }: { newFile: FileUpload }) => {
1230
+ const uploadFile = useStableCallback(async ({ newFile }: { newFile: FileUpload }) => {
1212
1231
  const { file, id } = newFile;
1213
1232
 
1214
1233
  // The file name can have special characters, so we escape it.
@@ -1251,9 +1270,9 @@ export const MessageInputProvider = <
1251
1270
  }
1252
1271
  handleFileOrImageUploadError(error, false, id);
1253
1272
  }
1254
- };
1273
+ });
1255
1274
 
1256
- const uploadImage = async ({ newImage }: { newImage: ImageUpload }) => {
1275
+ const uploadImage = useStableCallback(async ({ newImage }: { newImage: ImageUpload }) => {
1257
1276
  const { file, id } = newImage || {};
1258
1277
 
1259
1278
  if (!file) {
@@ -1334,9 +1353,9 @@ export const MessageInputProvider = <
1334
1353
  }
1335
1354
  handleFileOrImageUploadError(error, true, id);
1336
1355
  }
1337
- };
1356
+ });
1338
1357
 
1339
- const uploadNewFile = async (file: File) => {
1358
+ const uploadNewFile = useStableCallback(async (file: File) => {
1340
1359
  try {
1341
1360
  const id: string = generateRandomId();
1342
1361
  const fileConfig = getFileUploadConfig();
@@ -1382,9 +1401,9 @@ export const MessageInputProvider = <
1382
1401
  } catch (error) {
1383
1402
  console.log('Error uploading file', error);
1384
1403
  }
1385
- };
1404
+ });
1386
1405
 
1387
- const uploadNewImage = async (image: Partial<Asset>) => {
1406
+ const uploadNewImage = useStableCallback(async (image: Partial<Asset>) => {
1388
1407
  try {
1389
1408
  const id = generateRandomId();
1390
1409
  const imageUploadConfig = getImageUploadConfig();
@@ -1430,15 +1449,15 @@ export const MessageInputProvider = <
1430
1449
  } catch (error) {
1431
1450
  console.log('Error uploading image', error);
1432
1451
  }
1433
- };
1452
+ });
1434
1453
 
1435
- const openPollCreationDialog = () => {
1454
+ const openPollCreationDialog = useStableCallback(() => {
1436
1455
  if (openPollCreationDialogFromContext) {
1437
1456
  openPollCreationDialogFromContext({ sendMessage });
1438
1457
  return;
1439
1458
  }
1440
1459
  defaultOpenPollCreationDialog();
1441
- };
1460
+ });
1442
1461
 
1443
1462
  const messageInputContext = useCreateMessageInputContext({
1444
1463
  appendText,
@@ -1523,3 +1542,10 @@ export const useMessageInputContext = <
1523
1542
 
1524
1543
  return contextValue;
1525
1544
  };
1545
+
1546
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
1547
+ const useStableCallback = <T extends Function>(callback: T): T => {
1548
+ const ref = useRef<T>(callback);
1549
+ ref.current = callback;
1550
+ return useCallback(((...args: unknown[]) => ref.current(...args)) as unknown as T, []);
1551
+ };