Skip to content

Commit db18efd

Browse files
authored
feat: add initializeOnMount prop to ChannelProps (#2113)
1 parent cd07418 commit db18efd

File tree

3 files changed

+87
-10
lines changed

3 files changed

+87
-10
lines changed

docusaurus/docs/React/components/core-components/channel.mdx

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,14 @@ The Giphy version to render - check the keys of the [Image Object](https://devel
329329
| ------ | -------------- |
330330
| string | 'fixed_height' |
331331

332+
### HeaderComponent
333+
334+
Custom UI component to render at the top of the `MessageList`.
335+
336+
| Type | Default |
337+
| --------- | ------- |
338+
| component | none |
339+
332340
### imageAttachmentSizeHandler
333341

334342
A custom function to provide size configuration for image attachments
@@ -337,13 +345,54 @@ A custom function to provide size configuration for image attachments
337345
| ---------------------------------------------------------------- |
338346
| `(a: Attachment, e: HTMLElement) => ImageAttachmentConfigration` |
339347

340-
### HeaderComponent
348+
### initializeOnMount
349+
350+
Allows to prevent triggering the `channel.watch()` (triggers channel query HTTP request) call when mounting the `Channel` component (the default behavior) with uninitialized (`channel.initialized`) `Channel` instance. 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 in the Stream's DB to a later point in time, for example, when a first message is sent:
351+
352+
```typescript jsx
353+
import {useCallback} from "react";
354+
import {
355+
getChannel,
356+
MessageInput as StreamMessageInput,
357+
MessageInputProps, MessageToSend,
358+
useChannelActionContext,
359+
useChatContext
360+
} from "stream-chat-react";
361+
import {Message, SendMessageOptions} from "stream-chat";
362+
363+
import {useChannelInitContext} from "../../context/ChannelInitProvider";
364+
import type { MyStreamChatGenerics } from "../../types";
365+
366+
export const MessageInput = (props: MessageInputProps) => {
367+
const {client} = useChatContext();
368+
const {sendMessage} = useChannelActionContext();
369+
const { setInitializedChannelOnMount} = useChannelInitContext();
370+
371+
const submitHandler: MessageInputProps['overrideSubmitHandler'] = useCallback(async (
372+
message: MessageToSend<MyStreamChatGenerics>,
373+
channelCid: string,
374+
customMessageData?: Partial<Message<MyStreamChatGenerics>>,
375+
options?: SendMessageOptions,
376+
) => {
377+
const [channelType, channelId] = channelCid.split(":");
378+
const channel = client.channel(channelType, channelId);
379+
if (!channel.initialized) {
380+
await getChannel({channel, client});
381+
setInitializedChannelOnMount(true);
382+
}
383+
384+
await sendMessage(message, customMessageData, options);
385+
}, [client, sendMessage, setInitializedChannelOnMount]);
341386

342-
Custom UI component to render at the top of the `MessageList`.
387+
return (
388+
<StreamMessageInput {...props} overrideSubmitHandler={submitHandler} />
389+
);
390+
};
391+
```
343392

344-
| Type | Default |
345-
| --------- | ------- |
346-
| component | none |
393+
| Type | Default |
394+
|---------|---------|
395+
| boolean | true |
347396

348397
### Input
349398

src/components/Channel/Channel.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ export type ChannelProps<
152152
EmptyPlaceholder?: React.ReactElement;
153153
/** 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) */
154154
EmptyStateIndicator?: ComponentContextValue<StreamChatGenerics>['EmptyStateIndicator'];
155-
/** A global flag to toggle the URL enrichment and link previews in `MessageInput` components.
155+
/**
156+
* A global flag to toggle the URL enrichment and link previews in `MessageInput` components.
156157
* By default, the feature is disabled. Can be overridden on Thread, MessageList level through additionalMessageInputProps
157158
* or directly on MessageInput level through urlEnrichmentConfig.
158159
*/
@@ -169,6 +170,12 @@ export type ChannelProps<
169170
HeaderComponent?: ComponentContextValue<StreamChatGenerics>['HeaderComponent'];
170171
/** A custom function to provide size configuration for image attachments */
171172
imageAttachmentSizeHandler?: ImageAttachmentSizeHandler;
173+
/**
174+
* Allows to prevent triggering the channel.watch() call when mounting the component.
175+
* That means that no channel data from the back-end will be received neither channel WS events will be delivered to the client.
176+
* Preventing to initialize the channel on mount allows us to postpone the channel creation to a later point in time.
177+
*/
178+
initializeOnMount?: boolean;
172179
/** 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) */
173180
Input?: ComponentContextValue<StreamChatGenerics>['Input'];
174181
/** Custom component to render link previews in message input **/
@@ -312,6 +319,7 @@ const ChannelInner = <
312319
dragAndDropWindow = false,
313320
emojiData = defaultEmojiData,
314321
enrichURLForPreviewConfig,
322+
initializeOnMount = true,
315323
LoadingErrorIndicator = DefaultLoadingErrorIndicator,
316324
LoadingIndicator = DefaultLoadingIndicator,
317325
maxNumberOfFiles,
@@ -478,7 +486,7 @@ const ChannelInner = <
478486
};
479487

480488
(async () => {
481-
if (!channel.initialized) {
489+
if (!channel.initialized && initializeOnMount) {
482490
try {
483491
// if active channel has been set without id, we will create a temporary channel id from its member IDs
484492
// to keep track of the /query request in progress. This is the same approach of generating temporary id
@@ -533,7 +541,7 @@ const ChannelInner = <
533541
client.off('user.deleted', handleEvent);
534542
notificationTimeouts.forEach(clearTimeout);
535543
};
536-
}, [channel.cid, doMarkReadRequest, channelConfig?.read_events]);
544+
}, [channel.cid, doMarkReadRequest, channelConfig?.read_events, initializeOnMount]);
537545

538546
useEffect(() => {
539547
if (!state.thread) return;

src/components/Channel/__tests__/Channel.test.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,13 +380,33 @@ describe('Channel', () => {
380380
jest.spyOn(channel, 'countUnread').mockImplementationOnce(() => 1);
381381
const doMarkReadRequest = jest.fn();
382382

383-
renderComponent({
384-
doMarkReadRequest,
383+
await act(() => {
384+
renderComponent({
385+
doMarkReadRequest,
386+
});
385387
});
386388

387389
await waitFor(() => expect(doMarkReadRequest).toHaveBeenCalledTimes(1));
388390
});
389391

392+
it('should not query the channel from the backend when initializeOnMount is disabled', async () => {
393+
const watchSpy = jest.spyOn(channel, 'watch').mockImplementationOnce();
394+
await act(() => {
395+
renderComponent({
396+
initializeOnMount: false,
397+
});
398+
});
399+
await waitFor(() => expect(watchSpy).not.toHaveBeenCalled());
400+
});
401+
402+
it('should query the channel from the backend when initializeOnMount is enabled (the default)', async () => {
403+
const watchSpy = jest.spyOn(channel, 'watch').mockImplementationOnce();
404+
await act(() => {
405+
renderComponent();
406+
});
407+
await waitFor(() => expect(watchSpy).toHaveBeenCalledTimes(1));
408+
});
409+
390410
describe('Children that consume the contexts set in Channel', () => {
391411
it('should expose the emoji config', async () => {
392412
let context;

0 commit comments

Comments
 (0)