Skip to content

Commit e6809bc

Browse files
authored
Merge pull request #2121 from GetStream/develop
v10.13.0
2 parents 02c5f0c + def7c42 commit e6809bc

File tree

17 files changed

+282
-58
lines changed

17 files changed

+282
-58
lines changed

docusaurus/docs/React/components/contexts/message-input-context.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,14 @@ Function that runs onSubmit to the underlying `textarea` component.
246246
| ---------------------------------------------------------------------- |
247247
| (event: React.BaseSyntheticEvent, customMessageData?: Message) => void |
248248

249+
### hideSendButton
250+
251+
Allows to hide MessageInput's send button. Used by `MessageSimple` to hide the send button in `EditMessageForm`. Received from `MessageInputProps`.
252+
253+
| Type | Default |
254+
|---------|---------|
255+
| boolean | false |
256+
249257
### imageOrder
250258

251259
The order in which image attachments have been added to the current message.

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

docusaurus/docs/React/components/message-input-components/message-input.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ If true, expands the text input vertically for new lines.
124124
| ------- | ------- |
125125
| boolean | true |
126126

127+
### hideSendButton
128+
129+
Allows to hide MessageInput's send button. Used by `MessageSimple` to hide the send button in `EditMessageForm`.
130+
131+
| Type | Default |
132+
|---------|---------|
133+
| boolean | false |
134+
127135
### Input
128136

129137
Custom UI component handling how the message input is rendered.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@
169169
"rollup-plugin-url": "^3.0.1",
170170
"rollup-plugin-visualizer": "^4.2.0",
171171
"semantic-release": "^19.0.5",
172-
"stream-chat": "^8.12.0",
172+
"stream-chat": "^8.12.4",
173173
"style-loader": "^2.0.0",
174174
"ts-jest": "^26.5.1",
175175
"typescript": "^4.7.4",

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;

src/components/ChannelList/__tests__/ChannelList.test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,35 @@ describe('ChannelList', () => {
324324
expect(results).toHaveNoViolations();
325325
});
326326

327+
it('should show unique channels', async () => {
328+
useMockedApis(chatClientUthred, [queryChannelsApi([testChannel1, testChannel2])]);
329+
const ChannelPreview = (props) => <div data-testid={props.channel.id} role='listitem' />;
330+
render(
331+
<Chat client={chatClientUthred}>
332+
<ChannelList filters={{}} options={{ limit: 2 }} Preview={ChannelPreview} />
333+
</Chat>,
334+
);
335+
336+
await waitFor(() => {
337+
expect(screen.getByTestId(testChannel1.channel.id)).toBeInTheDocument();
338+
expect(screen.getByTestId(testChannel2.channel.id)).toBeInTheDocument();
339+
expect(screen.getAllByRole('listitem')).toHaveLength(2);
340+
});
341+
342+
useMockedApis(chatClientUthred, [queryChannelsApi([testChannel1, testChannel3])]);
343+
344+
await act(() => {
345+
fireEvent.click(screen.getByTestId('load-more-button'));
346+
});
347+
348+
await waitFor(() => {
349+
expect(screen.getByTestId(testChannel1.channel.id)).toBeInTheDocument();
350+
expect(screen.getByTestId(testChannel2.channel.id)).toBeInTheDocument();
351+
expect(screen.getByTestId(testChannel3.channel.id)).toBeInTheDocument();
352+
expect(screen.getAllByRole('listitem')).toHaveLength(3);
353+
});
354+
});
355+
327356
describe('Default and custom active channel', () => {
328357
let setActiveChannel;
329358
const watchersConfig = { limit: 20, offset: 0 };

src/components/ChannelList/hooks/usePaginatedChannels.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useEffect, useMemo, useState } from 'react';
2+
import uniqBy from 'lodash.uniqby';
23

34
import { MAX_QUERY_CHANNELS_LIMIT } from '../utils';
45

@@ -52,7 +53,9 @@ export const usePaginatedChannels = <
5253
const channelQueryResponse = await client.queryChannels(filters, sort || {}, newOptions);
5354

5455
const newChannels =
55-
queryType === 'reload' ? channelQueryResponse : [...channels, ...channelQueryResponse];
56+
queryType === 'reload'
57+
? channelQueryResponse
58+
: uniqBy([...channels, ...channelQueryResponse], 'cid');
5659

5760
setChannels(newChannels);
5861
setHasNextPage(channelQueryResponse.length >= newOptions.limit);

src/components/Message/MessageSimple.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ const MessageSimpleWithContext = <
124124
<MessageInput
125125
clearEditingState={clearEditingState}
126126
grow
127+
hideSendButton
127128
Input={EditMessageInput}
128129
message={message}
129130
{...additionalMessageInputProps}

src/components/MessageInput/MessageInput.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ export type MessageInputProps<
6161
getDefaultValue?: () => string | string[];
6262
/** If true, expands the text input vertically for new lines */
6363
grow?: boolean;
64+
/** Allows to hide MessageInput's send button. */
65+
hideSendButton?: boolean;
6466
/** 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) */
6567
Input?: React.ComponentType<MessageInputProps<StreamChatGenerics, V>>;
6668
/** Max number of rows the underlying `textarea` component is allowed to grow */

0 commit comments

Comments
 (0)