stream-chat-react 12.15.0 → 12.15.2

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.
@@ -24,7 +24,7 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
24
24
  useEffect(() => {
25
25
  if (!client)
26
26
  return;
27
- const version = "12.15.0";
27
+ const version = "12.15.2";
28
28
  const userAgent = client.getUserAgent();
29
29
  if (!userAgent.includes('stream-chat-react')) {
30
30
  // result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
@@ -1,3 +1,4 @@
1
+ import { nanoid } from 'nanoid';
1
2
  import { StateStore } from 'stream-chat';
2
3
  /**
3
4
  * Keeps a map of Dialog objects.
@@ -14,7 +15,7 @@ export class DialogManager {
14
15
  this.state = new StateStore({
15
16
  dialogsById: {},
16
17
  });
17
- this.id = id ?? new Date().getTime().toString();
18
+ this.id = id ?? nanoid();
18
19
  }
19
20
  get openDialogCount() {
20
21
  return Object.values(this.state.getLatestValue().dialogsById).reduce((count, dialog) => {
@@ -1,5 +1,4 @@
1
- import { nanoid } from 'nanoid';
2
- import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
3
2
  import { UploadIcon as DefaultUploadIcon } from './icons';
4
3
  import { CHANNEL_CONTAINER_ID } from '../Channel/constants';
5
4
  import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog';
@@ -10,11 +9,12 @@ import { Portal } from '../Portal/Portal';
10
9
  import { UploadFileInput } from '../ReactFileUtilities';
11
10
  import { useChannelStateContext, useComponentContext, useMessageInputContext, useTranslationContext, } from '../../context';
12
11
  import { AttachmentSelectorContextProvider, useAttachmentSelectorContext, } from '../../context/AttachmentSelectorContext';
12
+ import { useStableId } from '../UtilityComponents/useStableId';
13
13
  export const SimpleAttachmentSelector = () => {
14
14
  const { AttachmentSelectorInitiationButtonContents, FileUploadIcon = DefaultUploadIcon, } = useComponentContext();
15
15
  const inputRef = useRef(null);
16
16
  const [labelElement, setLabelElement] = useState(null);
17
- const id = useMemo(() => nanoid(), []);
17
+ const id = useStableId();
18
18
  useEffect(() => {
19
19
  if (!labelElement)
20
20
  return;
@@ -9,6 +9,7 @@ import { useComponentContext, } from '../../context/ComponentContext';
9
9
  import { MessageInputContextProvider } from '../../context/MessageInputContext';
10
10
  import { DialogManagerProvider } from '../../context';
11
11
  import { useRegisterDropHandlers } from './WithDragAndDropUpload';
12
+ import { useStableId } from '../UtilityComponents/useStableId';
12
13
  const MessageInputProvider = (props) => {
13
14
  const cooldownTimerState = useCooldownTimer();
14
15
  const messageInputState = useMessageInputState(props);
@@ -27,10 +28,11 @@ const UnMemoizedMessageInput = (props) => {
27
28
  const { Input: PropInput } = props;
28
29
  const { dragAndDropWindow } = useChannelStateContext();
29
30
  const { Input: ContextInput, TriggerProvider = DefaultTriggerProvider } = useComponentContext('MessageInput');
31
+ const id = useStableId();
30
32
  const Input = PropInput || ContextInput || MessageInputFlat;
31
33
  const dialogManagerId = props.isThreadInput
32
- ? 'message-input-dialog-manager-thread'
33
- : 'message-input-dialog-manager';
34
+ ? `message-input-dialog-manager-thread-${id}`
35
+ : `message-input-dialog-manager-${id}`;
34
36
  if (dragAndDropWindow)
35
37
  return (React.createElement(DialogManagerProvider, { id: dialogManagerId },
36
38
  React.createElement(TriggerProvider, null,
@@ -38,9 +38,13 @@ export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrich
38
38
  if (someAttachmentsUploading) {
39
39
  return addNotification(t('Wait until all attachments have uploaded'), 'error');
40
40
  }
41
- const attachmentsFromUploads = attachments
42
- .filter((att) => att.localMetadata?.uploadState !== 'failed' ||
43
- (findAndEnqueueURLsToEnrich && !att.og_scrape_url))
41
+ const attachmentsWithoutLinkPreviews = attachments
42
+ .filter((att) => {
43
+ const isSuccessfulUpload = att.localMetadata?.uploadState === 'finished';
44
+ const isNotUpload = !att.localMetadata?.uploadState;
45
+ const isNotLinkPreview = !att.og_scrape_url;
46
+ return isNotLinkPreview && (isSuccessfulUpload || isNotUpload);
47
+ })
44
48
  .map((localAttachment) => {
45
49
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
46
50
  const { localMetadata: _, ...attachment } = localAttachment;
@@ -56,8 +60,7 @@ export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrich
56
60
  attachmentsFromLinkPreviews = someLinkPreviewsLoading
57
61
  ? []
58
62
  : Array.from(linkPreviews.values())
59
- .filter((linkPreview) => linkPreview.state === LinkPreviewState.LOADED &&
60
- !attachmentsFromUploads.find((attFromUpload) => attFromUpload.og_scrape_url === linkPreview.og_scrape_url))
63
+ .filter((linkPreview) => linkPreview.state === LinkPreviewState.LOADED)
61
64
  .map(
62
65
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
63
66
  ({ state: linkPreviewState, ...ogAttachment }) => ogAttachment);
@@ -66,7 +69,10 @@ export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrich
66
69
  (!someLinkPreviewsLoading && attachmentsFromLinkPreviews.length > 0) ||
67
70
  someLinkPreviewsDismissed;
68
71
  }
69
- const newAttachments = [...attachmentsFromUploads, ...attachmentsFromLinkPreviews];
72
+ const newAttachments = [
73
+ ...attachmentsWithoutLinkPreviews,
74
+ ...attachmentsFromLinkPreviews,
75
+ ];
70
76
  // Instead of checking if a user is still mentioned every time the text changes,
71
77
  // just filter out non-mentioned users before submit, which is cheaper
72
78
  // and allows users to easily undo any accidental deletion
@@ -17,6 +17,7 @@ import { LoadingIndicator as DefaultLoadingIndicator } from '../Loading';
17
17
  import { defaultPinPermissions, MESSAGE_ACTIONS } from '../Message/utils';
18
18
  import { TypingIndicator as DefaultTypingIndicator } from '../TypingIndicator';
19
19
  import { MessageListMainPanel as DefaultMessageListMainPanel } from './MessageListMainPanel';
20
+ import { useStableId } from '../UtilityComponents/useStableId';
20
21
  import { defaultRenderMessages } from './renderMessages';
21
22
  import { DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, DEFAULT_NEXT_CHANNEL_PAGE_SIZE, } from '../../constants/limits';
22
23
  const MessageListWithContext = (props) => {
@@ -123,10 +124,11 @@ const MessageListWithContext = (props) => {
123
124
  }
124
125
  // eslint-disable-next-line react-hooks/exhaustive-deps
125
126
  }, [highlightedMessageId]);
127
+ const id = useStableId();
126
128
  const showEmptyStateIndicator = elements.length === 0 && !threadList;
127
129
  const dialogManagerId = threadList
128
- ? 'message-list-dialog-manager-thread'
129
- : 'message-list-dialog-manager';
130
+ ? `message-list-dialog-manager-thread-${id}`
131
+ : `message-list-dialog-manager-${id}`;
130
132
  return (React.createElement(MessageListContextProvider, { value: { listElement, scrollToBottom } },
131
133
  React.createElement(MessageListMainPanel, null,
132
134
  React.createElement(DialogManagerProvider, { id: dialogManagerId },
@@ -21,6 +21,7 @@ import { useChatContext } from '../../context/ChatContext';
21
21
  import { useComponentContext, } from '../../context/ComponentContext';
22
22
  import { VirtualizedMessageListContextProvider } from '../../context/VirtualizedMessageListContext';
23
23
  import { DEFAULT_NEXT_CHANNEL_PAGE_SIZE } from '../../constants/limits';
24
+ import { useStableId } from '../UtilityComponents/useStableId';
24
25
  function captureResizeObserverExceededError(e) {
25
26
  if (e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
26
27
  e.message === 'ResizeObserver loop limit exceeded') {
@@ -194,11 +195,12 @@ const VirtualizedMessageListWithContext = (props) => {
194
195
  clearTimeout(scrollTimeout);
195
196
  };
196
197
  }, [highlightedMessageId, processedMessages]);
198
+ const id = useStableId();
197
199
  if (!processedMessages)
198
200
  return null;
199
201
  const dialogManagerId = threadList
200
- ? 'virtualized-message-list-dialog-manager-thread'
201
- : 'virtualized-message-list-dialog-manager';
202
+ ? `virtualized-message-list-dialog-manager-thread-${id}`
203
+ : `virtualized-message-list-dialog-manager-${id}`;
202
204
  return (React.createElement(VirtualizedMessageListContextProvider, { value: { scrollToBottom } },
203
205
  React.createElement(MessageListMainPanel, null,
204
206
  React.createElement(DialogManagerProvider, { id: dialogManagerId },
@@ -7,7 +7,7 @@ import { useStateStore } from '../../../../store';
7
7
  import { usePollContext, useTranslationContext } from '../../../../context';
8
8
  const pollStateSelector = (nextValue) => ({
9
9
  name: nextValue.name,
10
- options: nextValue.options,
10
+ options: [...nextValue.options],
11
11
  vote_counts_by_option: nextValue.vote_counts_by_option,
12
12
  });
13
13
  export const PollResults = ({ close, }) => {
@@ -64,8 +64,9 @@ export const PollCreationDialog = ({ close }) => {
64
64
  React.createElement("div", { className: clsx('str-chat__form__input-field__value') },
65
65
  React.createElement(FieldError, { className: 'str-chat__form__input-field__error', "data-testid": 'poll-max-votes-allowed-input-field-error', text: multipleAnswerCountError }),
66
66
  React.createElement("input", { id: 'max_votes_allowed', onChange: (e) => {
67
- const isValidValue = !e.target.value ||
68
- e.target.value.match(VALID_MAX_VOTES_VALUE_REGEX);
67
+ const isValidValue = e.target.validity.valid &&
68
+ (!e.target.value ||
69
+ e.target.value.match(VALID_MAX_VOTES_VALUE_REGEX));
69
70
  if (!isValidValue) {
70
71
  setMultipleAnswerCountError(t('Type a number from 2 to 10'));
71
72
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The ID is generated using the `nanoid` library and is memoized to ensure
3
+ * that it remains the same across renders unless the key changes.
4
+ */
5
+ export declare const useStableId: (key?: string) => string;
@@ -0,0 +1,11 @@
1
+ import { nanoid } from 'nanoid';
2
+ import { useMemo } from 'react';
3
+ /**
4
+ * The ID is generated using the `nanoid` library and is memoized to ensure
5
+ * that it remains the same across renders unless the key changes.
6
+ */
7
+ export const useStableId = (key) => {
8
+ // eslint-disable-next-line react-hooks/exhaustive-deps
9
+ const id = useMemo(() => nanoid(), [key]);
10
+ return id;
11
+ };