diff --git a/docusaurus/docs/React/components/core-components/channel.mdx b/docusaurus/docs/React/components/core-components/channel.mdx
index 4ff40420c..a858655bb 100644
--- a/docusaurus/docs/React/components/core-components/channel.mdx
+++ b/docusaurus/docs/React/components/core-components/channel.mdx
@@ -176,6 +176,39 @@ Custom UI component to display a user's avatar.
| --------- | ---------------------------------------------------------- |
| component | |
+### channelQueryOptions
+
+Optional configuration parameters used for the initial channel query. Applied only if the value of `channel.initialized` is false. If the channel instance has already been initialized (channel has been queried), then the channel query will be skipped and channelQueryOptions will not be applied.
+
+In the example below, we specify, that the first page of messages when a channel is queried should have 20 messages (the default is 100). Note that the `channel` prop has to be passed along `channelQueryOptions`.
+
+```tsx
+import {ChannelQueryOptions} from "stream-chat";
+import {Channel, useChatContext} from "stream-chat-react";
+
+const channelQueryOptions: ChannelQueryOptions = {
+ messages: { limit: 20 },
+};
+
+type ChannelRendererProps = {
+ id: string;
+ type: string;
+};
+
+const ChannelRenderer = ({id, type}: ChannelRendererProps) => {
+ const { client } = useChatContext();
+ return (
+
+ {/* Channel children */}
+
+ );
+}
+```
+
+| Type |
+|-----------------------|
+| `ChannelQueryOptions` |
+
### CooldownTimer
Custom UI component to display the slow mode cooldown timer.
diff --git a/src/components/Channel/Channel.tsx b/src/components/Channel/Channel.tsx
index 3a0e7915f..81f9d0aa0 100644
--- a/src/components/Channel/Channel.tsx
+++ b/src/components/Channel/Channel.tsx
@@ -14,6 +14,7 @@ import throttle from 'lodash.throttle';
import {
ChannelAPIResponse,
ChannelMemberResponse,
+ ChannelQueryOptions,
ChannelState,
Event,
logChatPromiseExecution,
@@ -93,14 +94,9 @@ import {
} from '../Attachment/attachment-sizing';
import type { URLEnrichmentConfig } from '../MessageInput/hooks/useLinkPreviews';
-export type ChannelProps<
- StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
- V extends CustomTrigger = CustomTrigger
+type ChannelPropsForwardedToComponentContext<
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
> = {
- /** List of accepted file types */
- acceptedFiles?: string[];
- /** Custom handler function that runs when the active channel has unread messages (i.e., when chat is running on a separate browser tab) */
- activeUnreadHandler?: (unread: number, documentTitle: string) => void;
/** Custom UI component to display a message attachment, defaults to and accepts same props as: [Attachment](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Attachment/Attachment.tsx) */
Attachment?: ComponentContextValue['Attachment'];
/** Custom UI component to display a attachment previews in MessageInput, defaults to and accepts same props as: [Attachment](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/AttachmentPreviewList.tsx) */
@@ -113,74 +109,22 @@ export type ChannelProps<
AutocompleteSuggestionList?: ComponentContextValue['AutocompleteSuggestionList'];
/** UI component to display a user's avatar, defaults to and accepts same props as: [Avatar](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Avatar/Avatar.tsx) */
Avatar?: ComponentContextValue['Avatar'];
- /** The connected and active channel */
- channel?: StreamChannel;
/** Custom UI component to display the slow mode cooldown timer, defaults to and accepts same props as: [CooldownTimer](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/hooks/useCooldownTimer.tsx) */
CooldownTimer?: ComponentContextValue['CooldownTimer'];
/** Custom UI component for date separators, defaults to and accepts same props as: [DateSeparator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/DateSeparator.tsx) */
DateSeparator?: ComponentContextValue['DateSeparator'];
- /** Custom action handler to override the default `client.deleteMessage(message.id)` function */
- doDeleteMessageRequest?: (
- message: StreamMessage,
- ) => Promise>;
- /** Custom action handler to override the default `channel.markRead` request function (advanced usage only) */
- doMarkReadRequest?: (
- channel: StreamChannel,
- ) => Promise> | void;
- /** Custom action handler to override the default `channel.sendMessage` request function (advanced usage only) */
- doSendMessageRequest?: (
- channelId: string,
- message: Message,
- options?: SendMessageOptions,
- ) => ReturnType['sendMessage']> | void;
- /** Custom action handler to override the default `client.updateMessage` request function (advanced usage only) */
- doUpdateMessageRequest?: (
- cid: string,
- updatedMessage: UpdatedMessage,
- options?: UpdateMessageOptions,
- ) => ReturnType['updateMessage']>;
- /** If true, chat users will be able to drag and drop file uploads to the entire channel window */
- dragAndDropWindow?: boolean;
/** Custom UI component to override default edit message input, defaults to and accepts same props as: [EditMessageForm](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/EditMessageForm.tsx) */
EditMessageInput?: ComponentContextValue['EditMessageInput'];
- /** Custom UI component to override default `NimbleEmoji` from `emoji-mart` */
- Emoji?: EmojiContextValue['Emoji'];
- /** Custom prop to override default `facebook.json` emoji data set from `emoji-mart` */
- emojiData?: EmojiMartData;
/** Custom UI component for emoji button in input, defaults to and accepts same props as: [EmojiIconSmall](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/icons.tsx) */
EmojiIcon?: ComponentContextValue['EmojiIcon'];
- /** Custom UI component to override default `NimbleEmojiIndex` from `emoji-mart` */
- EmojiIndex?: EmojiContextValue['EmojiIndex'];
- /** Custom UI component to override default `NimblePicker` from `emoji-mart` */
- EmojiPicker?: EmojiContextValue['EmojiPicker'];
- /** Custom UI component to be shown if no active channel is set, defaults to null and skips rendering the Channel component */
- EmptyPlaceholder?: React.ReactElement;
/** Custom UI component to be displayed when the `MessageList` is empty, defaults to and accepts same props as: [EmptyStateIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/EmptyStateIndicator/EmptyStateIndicator.tsx) */
EmptyStateIndicator?: ComponentContextValue['EmptyStateIndicator'];
- /**
- * A global flag to toggle the URL enrichment and link previews in `MessageInput` components.
- * By default, the feature is disabled. Can be overridden on Thread, MessageList level through additionalMessageInputProps
- * or directly on MessageInput level through urlEnrichmentConfig.
- */
- enrichURLForPreview?: URLEnrichmentConfig['enrichURLForPreview'];
- /** Global configuration for link preview generation in all the MessageInput components */
- enrichURLForPreviewConfig?: Omit;
/** Custom UI component for file upload icon, defaults to and accepts same props as: [FileUploadIcon](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/icons.tsx) */
FileUploadIcon?: ComponentContextValue['FileUploadIcon'];
/** Custom UI component to render a Giphy preview in the `VirtualizedMessageList` */
GiphyPreviewMessage?: ComponentContextValue['GiphyPreviewMessage'];
- /** The giphy version to render - check the keys of the [Image Object](https://developers.giphy.com/docs/api/schema#image-object) for possible values. Uses 'fixed_height' by default */
- giphyVersion?: GiphyVersions;
/** Custom UI component to render at the top of the `MessageList` */
HeaderComponent?: ComponentContextValue['HeaderComponent'];
- /** A custom function to provide size configuration for image attachments */
- imageAttachmentSizeHandler?: ImageAttachmentSizeHandler;
- /**
- * Allows to prevent triggering the channel.watch() call when mounting the component.
- * That means that no channel data from the back-end will be received neither channel WS events will be delivered to the client.
- * 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 handling how the message input is rendered, defaults to and accepts the same props as [MessageInputFlat](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/MessageInputFlat.tsx) */
Input?: ComponentContextValue['Input'];
/** Custom component to render link previews in message input **/
@@ -189,8 +133,6 @@ export type ChannelProps<
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?: ComponentContextValue['LoadingIndicator'];
- /** Maximum number of attachments allowed per message */
- maxNumberOfFiles?: number;
/** 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) */
Message?: ComponentContextValue['Message'];
/** Custom UI component for a deleted message, defaults to and accepts same props as: [MessageDeleted](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageDeleted.tsx) */
@@ -211,14 +153,6 @@ export type ChannelProps<
MessageTimestamp?: ComponentContextValue['MessageTimestamp'];
/** Custom UI component for viewing message's image attachments, defaults to and accepts the same props as [ModalGallery](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Gallery/ModalGallery.tsx) */
ModalGallery?: ComponentContextValue['ModalGallery'];
- /** Whether to allow multiple attachment uploads */
- multipleUploads?: boolean;
- /** Custom action handler function to run on click of an @mention in a message */
- onMentionsClick?: OnMentionAction;
- /** Custom action handler function to run on hover of an @mention in a message */
- onMentionsHover?: OnMentionAction;
- /** If `dragAndDropWindow` prop is true, the props to pass to the MessageInput component (overrides props placed directly on MessageInput) */
- optionalMessageInputProps?: MessageInputProps;
/** Custom UI component to override default pinned message indicator, defaults to and accepts same props as: [PinIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/icons.tsx) */
PinIndicator?: ComponentContextValue['PinIndicator'];
/** Custom UI component to override quoted message UI on a sent message, defaults to and accepts same props as: [QuotedMessage](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/QuotedMessage.tsx) */
@@ -231,10 +165,6 @@ export type ChannelProps<
ReactionsList?: ComponentContextValue['ReactionsList'];
/** Custom UI component for send button, defaults to and accepts same props as: [SendButton](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/icons.tsx) */
SendButton?: ComponentContextValue['SendButton'];
- /** You can turn on/off thumbnail generation for video attachments */
- shouldGenerateVideoThumbnail?: boolean;
- /** If true, skips the message data string comparison used to memoize the current channel messages (helpful for channels with 1000s of messages) */
- skipMessageDataMemoization?: boolean;
/** Custom UI component that displays thread's parent or other message at the top of the `MessageList`, defaults to and accepts same props as [MessageSimple](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageSimple.tsx) */
ThreadHead?: React.ComponentType>;
/** Custom UI component to display the header of a `Thread`, defaults to and accepts same props as: [DefaultThreadHeader](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Thread/Thread.tsx) */
@@ -245,12 +175,99 @@ export type ChannelProps<
TriggerProvider?: ComponentContextValue['TriggerProvider'];
/** Custom UI component for the typing indicator, defaults to and accepts same props as: [TypingIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/TypingIndicator/TypingIndicator.tsx) */
TypingIndicator?: ComponentContextValue['TypingIndicator'];
- /** A custom function to provide size configuration for video attachments */
- videoAttachmentSizeHandler?: VideoAttachmentSizeHandler;
/** Custom UI component to display a message in the `VirtualizedMessageList`, does not have a default implementation */
VirtualMessage?: ComponentContextValue['VirtualMessage'];
};
+type ChannelPropsForwardedToEmojiContext = {
+ /** Custom UI component to override default `NimbleEmoji` from `emoji-mart` */
+ Emoji?: EmojiContextValue['Emoji'];
+ /** Custom prop to override default `facebook.json` emoji data set from `emoji-mart` */
+ emojiData?: EmojiMartData;
+ /** Custom UI component to override default `NimbleEmojiIndex` from `emoji-mart` */
+ EmojiIndex?: EmojiContextValue['EmojiIndex'];
+ /** Custom UI component to override default `NimblePicker` from `emoji-mart` */
+ EmojiPicker?: EmojiContextValue['EmojiPicker'];
+};
+
+export type ChannelProps<
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
+ V extends CustomTrigger = CustomTrigger
+> = ChannelPropsForwardedToComponentContext &
+ ChannelPropsForwardedToEmojiContext & {
+ /** List of accepted file types */
+ acceptedFiles?: string[];
+ /** Custom handler function that runs when the active channel has unread messages (i.e., when chat is running on a separate browser tab) */
+ activeUnreadHandler?: (unread: number, documentTitle: string) => void;
+ /** The connected and active channel */
+ channel?: StreamChannel;
+ /**
+ * Optional configuration parameters used for the initial channel query.
+ * Applied only if the value of channel.initialized is false.
+ * If the channel instance has already been initialized (channel has been queried),
+ * then the channel query will be skipped and channelQueryOptions will not be applied.
+ */
+ channelQueryOptions?: ChannelQueryOptions;
+ /** Custom action handler to override the default `client.deleteMessage(message.id)` function */
+ doDeleteMessageRequest?: (
+ message: StreamMessage,
+ ) => Promise>;
+ /** Custom action handler to override the default `channel.markRead` request function (advanced usage only) */
+ doMarkReadRequest?: (
+ channel: StreamChannel,
+ ) => Promise> | void;
+ /** Custom action handler to override the default `channel.sendMessage` request function (advanced usage only) */
+ doSendMessageRequest?: (
+ channelId: string,
+ message: Message,
+ options?: SendMessageOptions,
+ ) => ReturnType['sendMessage']> | void;
+ /** Custom action handler to override the default `client.updateMessage` request function (advanced usage only) */
+ doUpdateMessageRequest?: (
+ cid: string,
+ updatedMessage: UpdatedMessage,
+ options?: UpdateMessageOptions,
+ ) => ReturnType['updateMessage']>;
+ /** If true, chat users will be able to drag and drop file uploads to the entire channel window */
+ dragAndDropWindow?: boolean;
+ /** Custom UI component to be shown if no active channel is set, defaults to null and skips rendering the Channel component */
+ EmptyPlaceholder?: React.ReactElement;
+ /**
+ * A global flag to toggle the URL enrichment and link previews in `MessageInput` components.
+ * By default, the feature is disabled. Can be overridden on Thread, MessageList level through additionalMessageInputProps
+ * or directly on MessageInput level through urlEnrichmentConfig.
+ */
+ enrichURLForPreview?: URLEnrichmentConfig['enrichURLForPreview'];
+ /** Global configuration for link preview generation in all the MessageInput components */
+ enrichURLForPreviewConfig?: Omit;
+ /** The giphy version to render - check the keys of the [Image Object](https://developers.giphy.com/docs/api/schema#image-object) for possible values. Uses 'fixed_height' by default */
+ giphyVersion?: GiphyVersions;
+ /** A custom function to provide size configuration for image attachments */
+ imageAttachmentSizeHandler?: ImageAttachmentSizeHandler;
+ /**
+ * Allows to prevent triggering the channel.watch() call when mounting the component.
+ * That means that no channel data from the back-end will be received neither channel WS events will be delivered to the client.
+ * Preventing to initialize the channel on mount allows us to postpone the channel creation to a later point in time.
+ */
+ initializeOnMount?: boolean;
+ /** Maximum number of attachments allowed per message */
+ maxNumberOfFiles?: number;
+ /** Whether to allow multiple attachment uploads */
+ multipleUploads?: boolean;
+ /** Custom action handler function to run on click of an @mention in a message */
+ onMentionsClick?: OnMentionAction;
+ /** Custom action handler function to run on hover of an @mention in a message */
+ onMentionsHover?: OnMentionAction;
+ /** If `dragAndDropWindow` prop is true, the props to pass to the MessageInput component (overrides props placed directly on MessageInput) */
+ optionalMessageInputProps?: MessageInputProps;
+ /** You can turn on/off thumbnail generation for video attachments */
+ shouldGenerateVideoThumbnail?: boolean;
+ /** If true, skips the message data string comparison used to memoize the current channel messages (helpful for channels with 1000s of messages) */
+ skipMessageDataMemoization?: boolean;
+ /** A custom function to provide size configuration for video attachments */
+ videoAttachmentSizeHandler?: VideoAttachmentSizeHandler;
+ };
+
const UnMemoizedChannel = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
V extends CustomTrigger = CustomTrigger
@@ -298,7 +315,6 @@ const UnMemoizedChannel = <
return {EmptyPlaceholder}
;
}
- // @ts-ignore
return ;
};
@@ -317,6 +333,7 @@ const ChannelInner = <
acceptedFiles,
activeUnreadHandler,
channel,
+ channelQueryOptions,
children,
doDeleteMessageRequest,
doMarkReadRequest,
@@ -359,7 +376,7 @@ const ChannelInner = <
const [state, dispatch] = useReducer>(
channelReducer,
- // channel.initialized === false if client.channels() was not called, e.g. ChannelList is not used
+ // channel.initialized === false if client.channel().query() was not called, e.g. ChannelList is not used
// => Channel will call channel.watch() in useLayoutEffect => state.loading is used to signal the watch() call state
{ ...initialState, loading: !channel.initialized },
);
@@ -512,7 +529,7 @@ const ChannelInner = <
}
}
}
- await getChannel({ channel, client, members });
+ await getChannel({ channel, client, members, options: channelQueryOptions });
const config = channel.getConfig();
setChannelConfig(config);
} catch (e) {
@@ -547,7 +564,13 @@ const ChannelInner = <
client.off('user.deleted', handleEvent);
notificationTimeouts.forEach(clearTimeout);
};
- }, [channel.cid, doMarkReadRequest, channelConfig?.read_events, initializeOnMount]);
+ }, [
+ channel.cid,
+ channelQueryOptions,
+ doMarkReadRequest,
+ channelConfig?.read_events,
+ initializeOnMount,
+ ]);
useEffect(() => {
if (!state.thread) return;
diff --git a/src/components/Channel/__tests__/Channel.test.js b/src/components/Channel/__tests__/Channel.test.js
index 38219cb87..7b201b63a 100644
--- a/src/components/Channel/__tests__/Channel.test.js
+++ b/src/components/Channel/__tests__/Channel.test.js
@@ -272,7 +272,26 @@ describe('Channel', () => {
renderComponent({ channel, chatClient });
});
- await waitFor(() => expect(watchSpy).toHaveBeenCalledTimes(1));
+ await waitFor(() => {
+ expect(watchSpy).toHaveBeenCalledTimes(1);
+ expect(watchSpy).toHaveBeenCalledWith(undefined);
+ });
+ });
+
+ it('should apply channelQueryOptions to channel watch call', async () => {
+ const { channel, chatClient } = await initClient();
+ const watchSpy = jest.spyOn(channel, 'watch');
+ const channelQueryOptions = {
+ messages: { limit: 20 },
+ };
+ await act(() => {
+ renderComponent({ channel, channelQueryOptions, chatClient });
+ });
+
+ await waitFor(() => {
+ expect(watchSpy).toHaveBeenCalledTimes(1);
+ expect(watchSpy).toHaveBeenCalledWith(channelQueryOptions);
+ });
});
it('should not call watch the current channel on mount if channel is initialized', async () => {
@@ -375,7 +394,7 @@ describe('Channel', () => {
// first, wait for the effect in which the channel is watched,
// so we know the event listener is added to the document.
- await waitFor(() => expect(watchSpy).toHaveBeenCalledWith());
+ await waitFor(() => expect(watchSpy).toHaveBeenCalledWith(undefined));
setTimeout(() => fireEvent(document, new Event('visibilitychange')), 0);
await waitFor(() => expect(markReadSpy).toHaveBeenCalledWith());
diff --git a/src/utils/getChannel.ts b/src/utils/getChannel.ts
index 0a388f6bb..00962c571 100644
--- a/src/utils/getChannel.ts
+++ b/src/utils/getChannel.ts
@@ -1,4 +1,9 @@
-import type { Channel, QueryChannelAPIResponse, StreamChat } from 'stream-chat';
+import type {
+ Channel,
+ ChannelQueryOptions,
+ QueryChannelAPIResponse,
+ StreamChat,
+} from 'stream-chat';
import type { DefaultStreamChatGenerics } from '../types/types';
/**
@@ -17,12 +22,14 @@ type GetChannelParams<
channel?: Channel;
id?: string;
members?: string[];
+ options?: ChannelQueryOptions;
type?: string;
};
/**
* Calls channel.watch() if it was not already recently called. Waits for watch promise to resolve even if it was invoked previously.
* @param client
* @param members
+ * @param options
* @param type
* @param id
* @param channel
@@ -34,6 +41,7 @@ export const getChannel = async <
client,
id,
members,
+ options,
type,
}: GetChannelParams) => {
if (!channel && !type) {
@@ -60,7 +68,7 @@ export const getChannel = async <
if (queryPromise) {
await queryPromise;
} else {
- WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid] = theChannel.watch();
+ WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid] = theChannel.watch(options);
await WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid];
delete WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid];
}