diff --git a/examples/vite/src/App.tsx b/examples/vite/src/App.tsx
index bf36745609..9c9d6dc48d 100644
--- a/examples/vite/src/App.tsx
+++ b/examples/vite/src/App.tsx
@@ -20,6 +20,7 @@ import {
useCreateChatClient,
VirtualizedMessageList as MessageList,
Window,
+ WithComponents,
} from 'stream-chat-react';
import { createTextComposerEmojiMiddleware, EmojiPicker } from 'stream-chat-react/emojis';
import { init, SearchIndex } from 'emoji-mart';
@@ -103,36 +104,38 @@ const App = () => {
if (!chatClient) return <>Loading...>;
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/src/components/Channel/Channel.tsx b/src/components/Channel/Channel.tsx
index 32fc8fe0fb..c7311fe534 100644
--- a/src/components/Channel/Channel.tsx
+++ b/src/components/Channel/Channel.tsx
@@ -41,7 +41,6 @@ import { useIsMounted } from './hooks/useIsMounted';
import type { OnMentionAction } from './hooks/useMentionsHandlers';
import { useMentionsHandlers } from './hooks/useMentionsHandlers';
-import type { LoadingErrorIndicatorProps } from '../Loading';
import {
LoadingErrorIndicator as DefaultLoadingErrorIndicator,
LoadingChannel as DefaultLoadingIndicator,
@@ -50,7 +49,6 @@ import {
import type {
ChannelActionContextValue,
ChannelNotifications,
- ComponentContextValue,
MarkReadWrapperOptions,
} from '../../context';
import {
@@ -58,8 +56,8 @@ import {
ChannelStateProvider,
TypingProvider,
useChatContext,
+ useComponentContext,
useTranslationContext,
- WithComponents,
} from '../../context';
import { CHANNEL_CONTAINER_ID } from './constants';
@@ -93,79 +91,7 @@ import {
import { useSearchFocusedMessage } from '../../experimental/Search/hooks';
import { WithAudioPlayback } from '../AudioPlayback';
-type ChannelPropsForwardedToComponentContext = Pick<
- ComponentContextValue,
- | 'Attachment'
- | 'AttachmentPreviewList'
- | 'AttachmentSelector'
- | 'AttachmentSelectorInitiationButtonContents'
- | 'AudioRecorder'
- | 'AutocompleteSuggestionItem'
- | 'AutocompleteSuggestionList'
- | 'Avatar'
- | 'BaseImage'
- | 'CooldownTimer'
- | 'CustomMessageActionsList'
- | 'DateSeparator'
- | 'EditMessageInput'
- | 'EditMessageModal'
- | 'EmojiPicker'
- | 'emojiSearchIndex'
- | 'EmptyStateIndicator'
- | 'FileUploadIcon'
- | 'GiphyPreviewMessage'
- | 'HeaderComponent'
- | 'Input'
- | 'LinkPreviewList'
- | 'LoadingIndicator'
- | 'ShareLocationDialog'
- | 'Message'
- | 'MessageActions'
- | 'MessageBouncePrompt'
- | 'MessageBlocked'
- | 'MessageDeleted'
- | 'MessageIsThreadReplyInChannelButtonIndicator'
- | 'MessageListNotifications'
- | 'MessageListMainPanel'
- | 'MessageNotification'
- | 'MessageOptions'
- | 'MessageRepliesCountButton'
- | 'MessageStatus'
- | 'MessageSystem'
- | 'MessageTimestamp'
- | 'Modal'
- | 'ModalGallery'
- | 'PinIndicator'
- | 'PollActions'
- | 'PollContent'
- | 'PollCreationDialog'
- | 'PollHeader'
- | 'PollOptionSelector'
- | 'QuotedMessage'
- | 'QuotedMessagePreview'
- | 'QuotedPoll'
- | 'reactionOptions'
- | 'ReactionSelector'
- | 'ReactionsList'
- | 'ReactionsListModal'
- | 'ReminderNotification'
- | 'SendButton'
- | 'SendToChannelCheckbox'
- | 'StartRecordingAudioButton'
- | 'TextareaComposer'
- | 'ThreadHead'
- | 'ThreadHeader'
- | 'ThreadStart'
- | 'Timestamp'
- | 'TypingIndicator'
- | 'UnreadMessagesNotification'
- | 'UnreadMessagesSeparator'
- | 'VirtualMessage'
- | 'StopAIGenerationButton'
- | 'StreamedMessageText'
->;
-
-export type ChannelProps = ChannelPropsForwardedToComponentContext & {
+export type ChannelProps = {
/** Custom handler function that runs when the active channel has unread messages and the app is running on a separate browser tab */
activeUnreadHandler?: (unread: number, documentTitle: string) => void;
/** Allows multiple audio players to play the audio at the same time. Disabled by default. */
@@ -213,8 +139,6 @@ export type ChannelProps = ChannelPropsForwardedToComponentContext & {
* Preventing to initialize the channel on mount allows us to postpone the channel creation to a later point in time.
*/
initializeOnMount?: boolean;
- /** Custom UI component to be shown if the channel query fails, defaults to and accepts same props as: [LoadingErrorIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Loading/LoadingErrorIndicator.tsx) */
- LoadingErrorIndicator?: React.ComponentType;
/** Configuration parameter to mark the active channel as read when mounted (opened). By default, the channel is marked read on mount. */
markReadOnMount?: boolean;
/** Custom action handler function to run on click of an @mention in a message */
@@ -247,12 +171,9 @@ const ChannelContainer = ({
};
const UnMemoizedChannel = (props: PropsWithChildren) => {
- const {
- channel: propsChannel,
- EmptyPlaceholder = null,
- LoadingErrorIndicator,
- LoadingIndicator = DefaultLoadingIndicator,
- } = props;
+ const { channel: propsChannel, EmptyPlaceholder = null } = props;
+ const { LoadingErrorIndicator, LoadingIndicator = DefaultLoadingIndicator } =
+ useComponentContext();
const { channel: contextChannel, channelsQueryState } = useChatContext('Channel');
@@ -300,13 +221,15 @@ const ChannelInner = (
doSendMessageRequest,
doUpdateMessageRequest,
initializeOnMount = true,
- LoadingErrorIndicator = DefaultLoadingErrorIndicator,
- LoadingIndicator = DefaultLoadingIndicator,
markReadOnMount = true,
onMentionsClick,
onMentionsHover,
skipMessageDataMemoization,
} = props;
+ const {
+ LoadingErrorIndicator = DefaultLoadingErrorIndicator,
+ LoadingIndicator = DefaultLoadingIndicator,
+ } = useComponentContext();
const channelQueryOptions: ChannelQueryOptions & {
messages: { limit: number };
@@ -1203,149 +1126,6 @@ const ChannelInner = (
],
);
- const componentContextValue: Partial = useMemo(
- () => ({
- Attachment: props.Attachment,
- AttachmentPreviewList: props.AttachmentPreviewList,
- AttachmentSelector: props.AttachmentSelector,
- AttachmentSelectorInitiationButtonContents:
- props.AttachmentSelectorInitiationButtonContents,
- AudioRecorder: props.AudioRecorder,
- AutocompleteSuggestionItem: props.AutocompleteSuggestionItem,
- AutocompleteSuggestionList: props.AutocompleteSuggestionList,
- Avatar: props.Avatar,
- BaseImage: props.BaseImage,
- CooldownTimer: props.CooldownTimer,
- CustomMessageActionsList: props.CustomMessageActionsList,
- DateSeparator: props.DateSeparator,
- EditMessageInput: props.EditMessageInput,
- EditMessageModal: props.EditMessageModal,
- EmojiPicker: props.EmojiPicker,
- emojiSearchIndex: props.emojiSearchIndex,
- EmptyStateIndicator: props.EmptyStateIndicator,
- FileUploadIcon: props.FileUploadIcon,
- GiphyPreviewMessage: props.GiphyPreviewMessage,
- HeaderComponent: props.HeaderComponent,
- Input: props.Input,
- LinkPreviewList: props.LinkPreviewList,
- LoadingIndicator: props.LoadingIndicator,
- Message: props.Message,
- MessageActions: props.MessageActions,
- MessageBlocked: props.MessageBlocked,
- MessageBouncePrompt: props.MessageBouncePrompt,
- MessageDeleted: props.MessageDeleted,
- MessageIsThreadReplyInChannelButtonIndicator:
- props.MessageIsThreadReplyInChannelButtonIndicator,
- MessageListNotifications: props.MessageListNotifications,
- MessageNotification: props.MessageNotification,
- MessageOptions: props.MessageOptions,
- MessageRepliesCountButton: props.MessageRepliesCountButton,
- MessageStatus: props.MessageStatus,
- MessageSystem: props.MessageSystem,
- MessageTimestamp: props.MessageTimestamp,
- Modal: props.Modal,
- ModalGallery: props.ModalGallery,
- PinIndicator: props.PinIndicator,
- PollActions: props.PollActions,
- PollContent: props.PollContent,
- PollCreationDialog: props.PollCreationDialog,
- PollHeader: props.PollHeader,
- PollOptionSelector: props.PollOptionSelector,
- QuotedMessage: props.QuotedMessage,
- QuotedMessagePreview: props.QuotedMessagePreview,
- QuotedPoll: props.QuotedPoll,
- reactionOptions: props.reactionOptions,
- ReactionSelector: props.ReactionSelector,
- ReactionsList: props.ReactionsList,
- ReactionsListModal: props.ReactionsListModal,
- ReminderNotification: props.ReminderNotification,
- SendButton: props.SendButton,
- SendToChannelCheckbox: props.SendToChannelCheckbox,
- ShareLocationDialog: props.ShareLocationDialog,
- StartRecordingAudioButton: props.StartRecordingAudioButton,
- StopAIGenerationButton: props.StopAIGenerationButton,
- StreamedMessageText: props.StreamedMessageText,
- TextareaComposer: props.TextareaComposer,
- ThreadHead: props.ThreadHead,
- ThreadHeader: props.ThreadHeader,
- ThreadStart: props.ThreadStart,
- Timestamp: props.Timestamp,
- TypingIndicator: props.TypingIndicator,
- UnreadMessagesNotification: props.UnreadMessagesNotification,
- UnreadMessagesSeparator: props.UnreadMessagesSeparator,
- VirtualMessage: props.VirtualMessage,
- }),
- [
- props.Attachment,
- props.AttachmentPreviewList,
- props.AttachmentSelector,
- props.AttachmentSelectorInitiationButtonContents,
- props.AudioRecorder,
- props.AutocompleteSuggestionItem,
- props.AutocompleteSuggestionList,
- props.Avatar,
- props.BaseImage,
- props.CooldownTimer,
- props.CustomMessageActionsList,
- props.DateSeparator,
- props.EditMessageInput,
- props.EditMessageModal,
- props.EmojiPicker,
- props.emojiSearchIndex,
- props.EmptyStateIndicator,
- props.FileUploadIcon,
- props.GiphyPreviewMessage,
- props.HeaderComponent,
- props.Input,
- props.LinkPreviewList,
- props.LoadingIndicator,
- props.Message,
- props.MessageActions,
- props.MessageBlocked,
- props.MessageBouncePrompt,
- props.MessageDeleted,
- props.MessageIsThreadReplyInChannelButtonIndicator,
- props.MessageListNotifications,
- props.MessageNotification,
- props.MessageOptions,
- props.MessageRepliesCountButton,
- props.MessageStatus,
- props.MessageSystem,
- props.MessageTimestamp,
- props.Modal,
- props.ModalGallery,
- props.PinIndicator,
- props.PollActions,
- props.PollContent,
- props.PollCreationDialog,
- props.PollHeader,
- props.PollOptionSelector,
- props.QuotedMessage,
- props.QuotedMessagePreview,
- props.QuotedPoll,
- props.reactionOptions,
- props.ReactionSelector,
- props.ReactionsList,
- props.ReactionsListModal,
- props.ReminderNotification,
- props.SendButton,
- props.SendToChannelCheckbox,
- props.ShareLocationDialog,
- props.StartRecordingAudioButton,
- props.StopAIGenerationButton,
- props.StreamedMessageText,
- props.TextareaComposer,
- props.ThreadHead,
- props.ThreadHeader,
- props.ThreadStart,
- props.Timestamp,
- props.TypingIndicator,
- props.UnreadMessagesNotification,
- props.UnreadMessagesSeparator,
- props.VirtualMessage,
- ],
- );
-
const typingContextValue = useCreateTypingContext({
typing,
});
@@ -1378,13 +1158,11 @@ const ChannelInner = (
-
-
-
- {children}
-
-
-
+
+
+ {children}
+
+
diff --git a/src/components/Channel/__tests__/Channel.test.js b/src/components/Channel/__tests__/Channel.test.js
index db59877d03..7eae03b588 100644
--- a/src/components/Channel/__tests__/Channel.test.js
+++ b/src/components/Channel/__tests__/Channel.test.js
@@ -29,7 +29,7 @@ import {
} from '../../../mock-builders';
import { MessageList } from '../../MessageList';
import { Thread } from '../../Thread';
-import { MessageProvider } from '../../../context';
+import { MessageProvider, WithComponents } from '../../../context';
import { MessageActionsBox } from '../../MessageActions';
import { DEFAULT_THREAD_PAGE_SIZE } from '../../../constants/limits';
import { generateMessageDraft } from '../../../mock-builders/generator/messageDraft';
@@ -94,18 +94,22 @@ const renderComponent = async (props = {}, callback = () => {}) => {
const {
channel: channelFromProps,
chatClient: chatClientFromProps,
+ children,
+ components,
...channelProps
} = props;
let result;
await act(() => {
result = render(
-
-
-
- {channelProps.children}
-
-
- ,
+
+
+
+
+ {children}
+
+
+
+ ,
);
});
return result;
@@ -301,9 +305,13 @@ describe('Channel', () => {
},
}}
>
- {loadingText}
}>
- {childrenContent}
-
+ {loadingText}
,
+ }}
+ >
+ {childrenContent}
+
,
);
await waitFor(() => expect(screen.getByText(loadingText)).toBeInTheDocument());
@@ -323,9 +331,13 @@ describe('Channel', () => {
},
}}
>
- {error.message}
}>
- {childrenContent}
-
+ {error.message}
,
+ }}
+ >
+ {childrenContent}
+
,
);
await waitFor(() => expect(screen.getByText(errMsg)).toBeInTheDocument());
@@ -2141,10 +2153,12 @@ describe('Channel', () => {
);
await renderComponent(
{
- Avatar: MockAvatar,
channel,
chatClient,
children: ,
+ components: {
+ Avatar: MockAvatar,
+ },
},
callback?.(threadMessage),
);
@@ -2176,10 +2190,12 @@ describe('Channel', () => {
);
await renderComponent(
{
- Avatar: MockAvatar,
channel,
chatClient,
children: ,
+ components: {
+ Avatar: MockAvatar,
+ },
},
callback?.(threadMessage),
);
@@ -2293,7 +2309,9 @@ describe('Channel', () => {
[])} />
),
- CustomMessageActionsList,
+ components: {
+ CustomMessageActionsList,
+ },
});
await waitFor(() => {
diff --git a/src/components/Gallery/__tests__/Gallery.test.js b/src/components/Gallery/__tests__/Gallery.test.js
index 2e70659ad3..8ec4329861 100644
--- a/src/components/Gallery/__tests__/Gallery.test.js
+++ b/src/components/Gallery/__tests__/Gallery.test.js
@@ -11,7 +11,7 @@ import { Chat } from '../../Chat';
import { Gallery } from '../Gallery';
import { ComponentProvider } from '../../../context/ComponentContext';
-import { useChatContext } from '../../../context';
+import { useChatContext, WithComponents } from '../../../context';
let chatClient;
@@ -172,13 +172,14 @@ describe('Gallery', () => {
let result;
await act(() => {
result = render(
-
-
-
-
-
- ,
- ,
+
+
+
+
+
+
+
+ ,
);
});
expect(result.container).toMatchSnapshot();
diff --git a/src/components/Gallery/__tests__/Image.test.js b/src/components/Gallery/__tests__/Image.test.js
index 4f0421c4ba..868deba413 100644
--- a/src/components/Gallery/__tests__/Image.test.js
+++ b/src/components/Gallery/__tests__/Image.test.js
@@ -8,7 +8,7 @@ import { ImageComponent } from '../Image';
import { Chat } from '../../Chat';
import { Channel } from '../../Channel';
-import { useChatContext } from '../../../context';
+import { useChatContext, WithComponents } from '../../../context';
import { ComponentProvider } from '../../../context/ComponentContext';
import { initClientWithChannels } from '../../../mock-builders';
@@ -101,17 +101,18 @@ describe('Image', () => {
let result;
await act(() => {
result = render(
-
-
-
-
-
- ,
- ,
+
+
+
+
+
+
+
+ ,
);
});
expect(result.container).toMatchInlineSnapshot(`
@@ -133,7 +134,6 @@ describe('Image', () => {
/>
- ,
`);
});
diff --git a/src/components/Gallery/__tests__/__snapshots__/Gallery.test.js.snap b/src/components/Gallery/__tests__/__snapshots__/Gallery.test.js.snap
index 33562fa1c4..1e16dba9fe 100644
--- a/src/components/Gallery/__tests__/__snapshots__/Gallery.test.js.snap
+++ b/src/components/Gallery/__tests__/__snapshots__/Gallery.test.js.snap
@@ -226,6 +226,5 @@ exports[`Gallery should render custom BaseImage component 1`] = `
- ,
`;
diff --git a/src/components/MessageInput/__tests__/AttachmentPreviewList.test.js b/src/components/MessageInput/__tests__/AttachmentPreviewList.test.js
index 27ed1f76a4..7c8301bf92 100644
--- a/src/components/MessageInput/__tests__/AttachmentPreviewList.test.js
+++ b/src/components/MessageInput/__tests__/AttachmentPreviewList.test.js
@@ -16,7 +16,7 @@ import {
generateVoiceRecordingAttachment,
initClientWithChannels,
} from '../../../mock-builders';
-import { MessageProvider } from '../../../context';
+import { MessageProvider, WithComponents } from '../../../context';
jest.spyOn(window.HTMLMediaElement.prototype, 'pause').mockImplementation();
@@ -30,7 +30,7 @@ const renderComponent = async ({
attachments,
channel: customChannel,
client: customClient,
- componentCtx,
+ components,
coords,
editedMessage,
props,
@@ -47,27 +47,29 @@ const renderComponent = async ({
let result;
await act(() => {
result = render(
-
-
- {editedMessage ? (
-
+
+
+
+ {editedMessage ? (
+
+
+
+ ) : (
-
- ) : (
-
- )}
-
- ,
+ )}
+
+
+ ,
);
});
return { channel, ...result };
@@ -297,7 +299,7 @@ describe('AttachmentPreviewList', () => {
{ fallback: id },
),
),
- componentCtx: { BaseImage },
+ components: { BaseImage },
});
expect(container).toMatchSnapshot();
});
diff --git a/src/components/MessageInput/__tests__/EditMessageForm.test.js b/src/components/MessageInput/__tests__/EditMessageForm.test.js
index e2c2daeb84..de1a4918a7 100644
--- a/src/components/MessageInput/__tests__/EditMessageForm.test.js
+++ b/src/components/MessageInput/__tests__/EditMessageForm.test.js
@@ -27,6 +27,7 @@ import {
import { generatePoll } from '../../../mock-builders/generator/poll';
import { QuotedMessagePreview } from '../QuotedMessagePreview';
import { useMessageComposer as useMessageComposerMock } from '../hooks';
+import { WithComponents } from '../../../context';
jest.mock('../../Channel/utils', () => ({
...jest.requireActual('../../Channel/utils'),
@@ -119,6 +120,7 @@ const renderComponent = async ({
channelData = [],
channelProps = {},
chatContextOverrides = {},
+ components = {},
customChannel,
customClient,
CustomStateSetter = null,
@@ -140,26 +142,28 @@ const renderComponent = async ({
await act(() => {
renderResult = render(
-
-
+
-
- {CustomStateSetter && }
-
-
-
- ,
+
+ {CustomStateSetter && }
+
+
+
+
+ ,
);
});
@@ -249,7 +253,7 @@ describe(`EditMessageForm`, () => {
const CustomEmojiPicker = () => ;
const { customChannel, customClient } = await setup();
await renderComponent({
- channelProps: { EmojiPicker: CustomEmojiPicker },
+ components: { EmojiPicker: CustomEmojiPicker },
customChannel,
customClient,
});
@@ -366,7 +370,7 @@ describe(`EditMessageForm`, () => {
);
const { customChannel, customClient } = await setup();
const { container } = await renderComponent({
- channelProps: { FileUploadIcon },
+ components: { FileUploadIcon },
customChannel,
customClient,
});
@@ -394,7 +398,7 @@ describe(`EditMessageForm`, () => {
);
const { customChannel, customClient } = await setup();
const { container } = await renderComponent({
- channelProps: { AttachmentSelectorInitiationButtonContents, FileUploadIcon },
+ components: { AttachmentSelectorInitiationButtonContents, FileUploadIcon },
customChannel,
customClient,
});
@@ -1208,7 +1212,7 @@ describe(`EditMessageForm`, () => {
);
const { customChannel, customClient } = await setup();
const { container } = await renderComponent({
- channelProps: { AutocompleteSuggestionList },
+ components: { AutocompleteSuggestionList },
customChannel,
customClient,
});
@@ -1324,7 +1328,7 @@ describe(`EditMessageForm`, () => {
composition: messageWithQuotedMessage,
});
await renderComponent({
- channelProps: {
+ components: {
QuotedMessagePreview: (props) => (
),
@@ -1417,7 +1421,9 @@ describe(`EditMessageForm`, () => {
const QuotedPoll = () => {pollText}
;
await renderComponent({
- channelProps: { QuotedPoll },
+ components: {
+ QuotedPoll,
+ },
customChannel,
customClient,
});
diff --git a/src/components/MessageInput/__tests__/MessageInput.test.js b/src/components/MessageInput/__tests__/MessageInput.test.js
index 0ce72535ec..be4d7e440e 100644
--- a/src/components/MessageInput/__tests__/MessageInput.test.js
+++ b/src/components/MessageInput/__tests__/MessageInput.test.js
@@ -25,6 +25,7 @@ import {
} from '../../../mock-builders';
import { generatePoll } from '../../../mock-builders/generator/poll';
import { QuotedMessagePreview } from '../QuotedMessagePreview';
+import { WithComponents } from '../../../context';
expect.extend(toHaveNoViolations);
@@ -134,6 +135,7 @@ const renderComponent = async ({
channelData = [],
channelProps = {},
chatContextOverrides = {},
+ components = {},
customChannel,
customClient,
customUser,
@@ -155,21 +157,23 @@ const renderComponent = async ({
await act(() => {
renderResult = render(
-
-
-
-
-
-
-
- ,
+
+
+
+
+
+
+
+
+
+ ,
);
});
@@ -257,7 +261,7 @@ describe(`MessageInputFlat`, () => {
it('should render custom EmojiPicker', async () => {
const CustomEmojiPicker = () => ;
- await renderComponent({ channelProps: { EmojiPicker: CustomEmojiPicker } });
+ await renderComponent({ components: { EmojiPicker: CustomEmojiPicker } });
await waitFor(() => {
const c = screen.getByTestId('custom-emoji-picker');
@@ -319,7 +323,7 @@ describe(`MessageInputFlat`, () => {
);
- const { container } = await renderComponent({ channelProps: { FileUploadIcon } });
+ const { container } = await renderComponent({ components: { FileUploadIcon } });
const fileUploadIcon = await screen.findByTitle('NotFileUploadIcon');
@@ -344,7 +348,7 @@ describe(`MessageInputFlat`, () => {
);
const { container } = await renderComponent({
- channelProps: { AttachmentSelectorInitiationButtonContents, FileUploadIcon },
+ components: { AttachmentSelectorInitiationButtonContents, FileUploadIcon },
});
const fileUploadIcon = await screen.queryByTitle('NotFileUploadIcon');
@@ -368,7 +372,7 @@ describe(`MessageInputFlat`, () => {
const customTestId = 'custom-link-preview';
const CustomLinkPreviewList = () => ;
await renderComponent({
- channelProps: { LinkPreviewList: CustomLinkPreviewList },
+ components: { LinkPreviewList: CustomLinkPreviewList },
});
await act(async () => {
fireEvent.change(await screen.findByPlaceholderText(inputPlaceholder), {
@@ -1114,7 +1118,9 @@ describe(`MessageInputFlat`, () => {
);
const { customChannel, customClient } = await setup();
const { container } = await renderComponent({
- channelProps: { AutocompleteSuggestionList },
+ components: {
+ AutocompleteSuggestionList,
+ },
customChannel,
customClient,
});
@@ -1163,7 +1169,7 @@ describe(`MessageInputFlat`, () => {
});
const fn = jest.fn().mockReturnValue({m.text}
);
await renderComponent({
- channelProps: {
+ components: {
QuotedMessagePreview: (props) => (
),
@@ -1232,7 +1238,7 @@ describe(`MessageInputFlat`, () => {
const QuotedPoll = () => {pollText}
;
await renderComponent({
- channelProps: { QuotedPoll },
+ components: { QuotedPoll },
customChannel: channel,
customClient: client,
messageContextOverrides: {
diff --git a/src/components/MessageList/__tests__/MessageList.test.js b/src/components/MessageList/__tests__/MessageList.test.js
index efe309095b..d4dba32932 100644
--- a/src/components/MessageList/__tests__/MessageList.test.js
+++ b/src/components/MessageList/__tests__/MessageList.test.js
@@ -171,10 +171,12 @@ describe('MessageList', () => {
it('Message UI components should render `Avatar` when the custom prop is provided', async () => {
const renderResult = renderComponent({
channelProps: {
- Avatar,
channel,
},
chatClient,
+ components: {
+ Avatar,
+ },
});
await waitFor(() => {
@@ -253,8 +255,9 @@ describe('MessageList', () => {
const Header = () => {headerText}
;
renderComponent({
- channelProps: { channel, HeaderComponent: Header },
+ channelProps: { channel },
chatClient,
+ components: { HeaderComponent: Header },
msgListProps: {
messages: [intro],
},
@@ -580,8 +583,9 @@ describe('MessageList', () => {
await act(() => {
renderComponent({
- channelProps: { channel, UnreadMessagesSeparator },
+ channelProps: { channel },
chatClient: client,
+ components: { UnreadMessagesSeparator },
msgListProps: { messages },
});
});
@@ -608,8 +612,9 @@ describe('MessageList', () => {
await act(() => {
renderComponent({
- channelProps: { channel, UnreadMessagesSeparator },
+ channelProps: { channel },
chatClient: client,
+ components: { UnreadMessagesSeparator },
msgListProps: { messages },
});
});
@@ -643,6 +648,7 @@ describe('MessageList', () => {
const setupTest = async ({
channelProps = {},
+ components = {},
dispatchMarkUnreadPayload = {},
entries,
msgListProps = {},
@@ -656,6 +662,7 @@ describe('MessageList', () => {
renderComponent({
channelProps: { channel, ...channelProps },
chatClient: client,
+ components,
msgListProps: { messages, ...msgListProps },
});
});
@@ -746,7 +753,9 @@ describe('MessageList', () => {
aaa
);
await setupTest({
- channelProps: { UnreadMessagesNotification },
+ components: {
+ UnreadMessagesNotification,
+ },
entries: observerEntriesScrolledBelowSeparator,
});
@@ -803,10 +812,12 @@ describe('MessageList', () => {
renderComponent({
channelProps: {
channel,
+ },
+ chatClient: client,
+ components: {
MessageListNotifications: MockMessageListNotifications,
MessageNotification: ScrollToBottomButton,
},
- chatClient: client,
msgListProps: { messages },
});
});
@@ -831,10 +842,12 @@ describe('MessageList', () => {
renderComponent({
channelProps: {
channel,
+ },
+ chatClient: client,
+ components: {
MessageListNotifications: MockMessageListNotifications,
MessageNotification: ScrollToBottomButton,
},
- chatClient: client,
msgListProps: { messages, threadList: true },
});
});
diff --git a/src/context/ComponentContext.tsx b/src/context/ComponentContext.tsx
index 40f9a7845b..2214bfbc45 100644
--- a/src/context/ComponentContext.tsx
+++ b/src/context/ComponentContext.tsx
@@ -16,6 +16,7 @@ import type {
EventComponentProps,
FixedHeightMessageProps,
GiphyPreviewMessageProps,
+ LoadingErrorIndicatorProps,
LoadingIndicatorProps,
MessageBouncePromptProps,
MessageDeletedProps,
@@ -118,6 +119,8 @@ export type ComponentContextValue = {
Input?: React.ComponentType;
/** Custom component to render link previews in message input **/
LinkPreviewList?: React.ComponentType;
+ /** Custom UI component to be shown if the channel query fails, defaults to and accepts same props as: [LoadingErrorIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Loading/LoadingErrorIndicator.tsx) */
+ LoadingErrorIndicator?: React.ComponentType;
/** Custom UI component to render while the `MessageList` is loading new messages, defaults to and accepts same props as: [LoadingIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Loading/LoadingIndicator.tsx) */
LoadingIndicator?: React.ComponentType;
/** Custom UI component to display a message in the standard `MessageList`, defaults to and accepts the same props as: [MessageSimple](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageSimple.tsx) */