Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: get correct lastMessage and latestMessagePreview from channel state #2535

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions docusaurus/docs/React/components/core-components/channel-list.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ re-setting the list state, you can customize behavior and UI.
| `channel.truncated` | Updates the channel | [onChannelTruncated](#onchanneltruncated) |
| `channel.updated` | Updates the channel | [onChannelUpdated](#onchannelupdated) |
| `channel.visible` | Adds channel to list | [onChannelVisible](#onchannelvisible) |
| `connection.recovered` | Forces a component render | N/A |
| `connection.recovered` | Forces a component render | N/A |
| `message.new` | Moves channel to top of list | [onMessageNewHandler](#onmessagenewhandler) |
| `notification.added_to_channel` | Moves channel to top of list and starts watching | [onAddedToChannel](#onaddedtochannel) |
| `notification.message_new` | Moves channel to top of list and starts watching | [onMessageNew](#onmessagenew) |
Expand Down Expand Up @@ -225,28 +225,26 @@ Custom function that handles the channel pagination.
Takes parameters:

| Parameter | Description |
|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `currentChannels` | The state of loaded `Channel` objects queried thus far. Has to be set with `setChannels` (see below). |
| `queryType` | A string indicating, whether the channels state has to be reset to the first page ('reload') or newly queried channels should be appended to the `currentChannels`. |
| `setChannels` | Function that allows us to set the channels state reflected in `currentChannels`. |
| `setHasNextPage` | Flag indicating whether there are more items to be loaded from the API. Should be infered from the comparison of the query result length and the query options limit. |

The function has to:

1. build / provide own query filters, sort and options parameters
2. query and append channels to the current channels state
3. update the `hasNext` pagination flag after each query with `setChannels` function

An example below implements a custom query function that uses different filters sequentially once a preceding filter is exhausted:

```ts
import uniqBy from "lodash.uniqby";
import uniqBy from 'lodash.uniqby';
import throttle from 'lodash.throttle';
import {useCallback, useRef} from 'react';
import {ChannelFilters, ChannelOptions, ChannelSort, StreamChat} from 'stream-chat';
import {
CustomQueryChannelParams,
useChatContext,
} from 'stream-chat-react';
import { useCallback, useRef } from 'react';
import { ChannelFilters, ChannelOptions, ChannelSort, StreamChat } from 'stream-chat';
import { CustomQueryChannelParams, useChatContext } from 'stream-chat-react';

const DEFAULT_PAGE_SIZE = 30 as const;

Expand Down Expand Up @@ -312,7 +310,7 @@ export const useCustomQueryChannels = () => {
It is recommended to control for duplicate requests by throttling the custom function calls.

| Type |
|---------------------------------------------------------------------------------------------------|
| ------------------------------------------------------------------------------------------------- |
| <GHComponentLink text='CustomQueryChannelsFn' path='/ChannelList/hooks/usePaginatedChannels.ts'/> |

### EmptyStateIndicator
Expand All @@ -332,6 +330,14 @@ for more information.
| ------ |
| object |

### getLatestMessagePreview

Custom function that generates the message preview in ChannelPreview component.

| Type |
| ------------------------------------------------------------------------------------------------------------------------------------- |
| `(channel: Channel, t: TranslationContextValue['t'], userLanguage: TranslationContextValue['userLanguage']) => string \| JSX.Element` |

### List

Custom UI component to display the container for the queried channels.
Expand Down Expand Up @@ -425,7 +431,7 @@ Function to override the default behavior when a message is received on a channe
Function to override the default behavior when a message is received on a channel being watched. Handles `message.new` event.

| Type |
|-------------------------------------------------------------------------------------------------------------------------------------|
| ----------------------------------------------------------------------------------------------------------------------------------- |
| `(setChannels: React.Dispatch<React.SetStateAction<Array<Channel<StreamChatGenerics>>>>, event: Event<StreamChatGenerics>) => void` |

### onRemovedFromChannel
Expand Down Expand Up @@ -491,7 +497,7 @@ const App = () => (
```

| Type | Default |
|--------|---------|
| ------ | ------- |
| number | 5000 |

### renderChannels
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ For even deeper customization of the channel list and channel previews, use the
To customize the `ChannelList` item UI simply pass your custom `Preview` component to the `ChannelList`. See [The Preview Prop Component](../../guides/customization/channel-list-preview.mdx#the-preview-prop-component) for the extended guide.

```tsx
const CustomChannelPreviewUI = ({ latestMessage, lastMessage }) => {
const CustomChannelPreviewUI = ({ latestMessagePreview, lastMessage }) => {
// "lastMessage" property is for the last
// message that has been interacted with (pinned/edited/deleted)

// to display last message of the channel use "latestMessage" property
return <span>{latestMessage}</span>;
// to display last message of the channel use "latestMessagePreview" property
return <span>{latestMessagePreview}</span>;
};

<ChannelList Preview={CustomChannelPreviewUI} />;
Expand Down Expand Up @@ -95,6 +95,14 @@ Title of channel to display.
| -------- |
| `string` |

### getLatestMessagePreview

Custom function that generates the message preview in ChannelPreview component.

| Type |
| ------------------------------------------------------------------------------------------------------------------------------------- |
| `(channel: Channel, t: TranslationContextValue['t'], userLanguage: TranslationContextValue['userLanguage']) => string \| JSX.Element` |

### lastMessage

The last message received in a channel.
Expand All @@ -105,6 +113,14 @@ The last message received in a channel.

### latestMessage

Deprecated, use `latestMessagePreview` instead.

| Type |
| ----------------------- |
| `string \| JSX.Element` |

### latestMessagePreview

Latest message preview to display. Will be either a string or a JSX.Element rendering markdown.

| Type |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ Custom class for the channel preview root
| -------- |
| `string` |

### getLatestMessagePreview

Custom function that generates the message preview in ChannelPreview component.

| Type |
| ------------------------------------------------------------------------------------------------------------------------------------- |
| `(channel: Channel, t: TranslationContextValue['t'], userLanguage: TranslationContextValue['userLanguage']) => string \| JSX.Element` |

### onSelect

Custom handler invoked when the `ChannelPreview` is clicked. The SDK uses `ChannelPreview` to display items of channel search results. There, behind the scenes, the new active channel is set.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ Let's implement a simple custom preview:
<TabItem value="js" label="React">

```jsx
const CustomChannelPreview = ({ displayImage, displayTitle, latestMessage }) => (
const CustomChannelPreview = ({ displayImage, displayTitle, latestMessagePreview }) => (
<div className='channel-preview'>
<img className='channel-preview__avatar' src={displayImage} alt='' />
<div className='channel-preview__main'>
<div className='channel-preview__header'>{displayTitle}</div>
<div className='channel-preview__message'>{latestMessage}</div>
<div className='channel-preview__message'>{latestMessagePreview}</div>
</div>
</div>
);
Expand Down Expand Up @@ -122,7 +122,7 @@ message in the channel:

```jsx
const CustomChannelPreview = (props) => {
const { channel, displayImage, displayTitle, latestMessage } = props;
const { channel, displayImage, displayTitle, latestMessagePreview } = props;
const { userLanguage } = useTranslationContext();
const latestMessageAt = channel.state.last_message_at;

Expand All @@ -146,7 +146,7 @@ const CustomChannelPreview = (props) => {
{timestamp}
</time>
</div>
<div className='channel-preview__message'>{latestMessage}</div>
<div className='channel-preview__message'>{latestMessagePreview}</div>
</div>
</div>
);
Expand Down Expand Up @@ -217,7 +217,7 @@ const CustomChannelPreview = (props) => {
activeChannel,
displayImage,
displayTitle,
latestMessage,
latestMessagePreview,
setActiveChannel,
} = props;
const latestMessageAt = channel.state.last_message_at;
Expand Down Expand Up @@ -252,7 +252,7 @@ const CustomChannelPreview = (props) => {
{timestamp}
</time>
</div>
<div className='channel-preview__message'>{latestMessage}</div>
<div className='channel-preview__message'>{latestMessagePreview}</div>
</div>
</button>
);
Expand Down
10 changes: 9 additions & 1 deletion src/components/ChannelList/ChannelList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { ChannelListContextProvider } from '../../context';
import { useChatContext } from '../../context/ChatContext';

import type { Channel, ChannelFilters, ChannelOptions, ChannelSort, Event } from 'stream-chat';

import type { TranslationContextValue } from '../../context/TranslationContext';
import type { DefaultStreamChatGenerics, PaginatorProps } from '../../types/types';

const DEFAULT_FILTERS = {};
Expand Down Expand Up @@ -70,6 +70,12 @@ export type ChannelListProps<
EmptyStateIndicator?: React.ComponentType<EmptyStateIndicatorProps>;
/** An object containing channel query filters */
filters?: ChannelFilters<StreamChatGenerics>;
/** Custom function that generates the message preview in ChannelPreview component */
getLatestMessagePreview?: (
channel: Channel<StreamChatGenerics>,
t: TranslationContextValue['t'],
userLanguage: TranslationContextValue['userLanguage'],
) => string | JSX.Element;
/** Custom UI component to display the container for the queried channels, defaults to and accepts same props as: [ChannelListMessenger](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelList/ChannelListMessenger.tsx) */
List?: React.ComponentType<ChannelListMessengerProps<StreamChatGenerics>>;
/** Custom UI component to display the loading error indicator, defaults to and accepts same props as: [ChatDown](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChatDown/ChatDown.tsx) */
Expand Down Expand Up @@ -168,6 +174,7 @@ const UnMemoizedChannelList = <
customQueryChannels,
EmptyStateIndicator = DefaultEmptyStateIndicator,
filters,
getLatestMessagePreview,
LoadingErrorIndicator = ChatDown,
LoadingIndicator = LoadingChannels,
List = ChannelListMessenger,
Expand Down Expand Up @@ -333,6 +340,7 @@ const UnMemoizedChannelList = <
channel: item,
// forces the update of preview component on channel update
channelUpdateCount,
getLatestMessagePreview,
key: item.cid,
Preview,
setActiveChannel,
Expand Down
34 changes: 32 additions & 2 deletions src/components/ChannelList/__tests__/ChannelList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ const channelsQueryStateMock = {
* to those components might end up breaking tests for ChannelList, which will be quite painful
* to debug then.
*/
const ChannelPreviewComponent = ({ channel, channelUpdateCount, latestMessage }) => (
const ChannelPreviewComponent = ({ channel, channelUpdateCount, latestMessagePreview }) => (
<div data-testid={channel.id} role='listitem'>
<div data-testid='channelUpdateCount'>{channelUpdateCount}</div>
<div>{channel.data.name}</div>
<div>{latestMessage}</div>
<div>{latestMessagePreview}</div>
</div>
);

Expand Down Expand Up @@ -457,6 +457,36 @@ describe('ChannelList', () => {
});
});

it('allows to customize latest message preview generation', async () => {
const previewText = 'custom preview text';
const getLatestMessagePreview = () => previewText;

useMockedApis(chatClient, [queryChannelsApi([testChannel1])]);
const { rerender } = render(
<Chat client={chatClient}>
<ChannelList filters={{}} options={{ limit: 2 }} />
</Chat>,
);

await waitFor(() => {
expect(screen.getByText('Nothing yet...')).toBeInTheDocument();
});

rerender(
<Chat client={chatClient}>
<ChannelList
filters={{}}
getLatestMessagePreview={getLatestMessagePreview}
options={{ limit: 2 }}
/>
</Chat>,
);

await waitFor(() => {
expect(screen.getByText(previewText)).toBeInTheDocument();
});
});

describe('Default and custom active channel', () => {
let setActiveChannel;
const watchersConfig = { limit: 20, offset: 0 };
Expand Down
37 changes: 27 additions & 10 deletions src/components/ChannelPreview/ChannelPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { ChannelPreviewMessenger } from './ChannelPreviewMessenger';
import { useIsChannelMuted } from './hooks/useIsChannelMuted';
import { useChannelPreviewInfo } from './hooks/useChannelPreviewInfo';
import { getLatestMessagePreview } from './utils';
import { getLatestMessagePreview as defaultGetLatestMessagePreview } from './utils';

import { ChatContextValue, useChatContext } from '../../context/ChatContext';
import { useTranslationContext } from '../../context/TranslationContext';
Expand All @@ -15,7 +15,7 @@ import type { Channel, Event } from 'stream-chat';
import type { AvatarProps } from '../Avatar/Avatar';

import type { StreamMessage } from '../../context/ChannelStateContext';

import type { TranslationContextValue } from '../../context/TranslationContext';
import type { DefaultStreamChatGenerics } from '../../types/types';

export type ChannelPreviewUIComponentProps<
Expand All @@ -29,8 +29,10 @@ export type ChannelPreviewUIComponentProps<
displayTitle?: string;
/** The last message received in a channel */
lastMessage?: StreamMessage<StreamChatGenerics>;
/** Latest message preview to display, will be a string or JSX element supporting markdown. */
/** @deprecated Use latestMessagePreview prop instead. */
latestMessage?: string | JSX.Element;
/** Latest message preview to display, will be a string or JSX element supporting markdown. */
latestMessagePreview?: string | JSX.Element;
/** Status describing whether own message has been delivered or read by another. If the last message is not an own message, then the status is undefined. */
messageDeliveryStatus?: MessageDeliveryStatus;
/** Number of unread Messages */
Expand All @@ -50,6 +52,12 @@ export type ChannelPreviewProps<
channelUpdateCount?: number;
/** Custom class for the channel preview root */
className?: string;
/** Custom function that generates the message preview in ChannelPreview component */
getLatestMessagePreview?: (
channel: Channel<StreamChatGenerics>,
t: TranslationContextValue['t'],
userLanguage: TranslationContextValue['userLanguage'],
) => string | JSX.Element;
key?: string;
/** Custom ChannelPreview click handler function */
onSelect?: (event: React.MouseEvent) => void;
Expand All @@ -66,7 +74,12 @@ export const ChannelPreview = <
>(
props: ChannelPreviewProps<StreamChatGenerics>,
) => {
const { channel, Preview = ChannelPreviewMessenger, channelUpdateCount } = props;
const {
channel,
Preview = ChannelPreviewMessenger,
channelUpdateCount,
getLatestMessagePreview = defaultGetLatestMessagePreview,
} = props;
const { channel: activeChannel, client, setActiveChannel } = useChatContext<StreamChatGenerics>(
'ChannelPreview',
);
Expand Down Expand Up @@ -123,26 +136,29 @@ export const ChannelPreview = <
useEffect(() => {
refreshUnreadCount();

const handleEvent = (event: Event<StreamChatGenerics>) => {
if (event.message) setLastMessage(event.message);
const handleEvent = () => {
setLastMessage(channel.state.latestMessages[channel.state.latestMessages.length - 1]);
refreshUnreadCount();
};

channel.on('message.new', handleEvent);
channel.on('message.updated', handleEvent);
channel.on('message.deleted', handleEvent);
channel.on('message.undeleted', handleEvent);
channel.on('channel.truncated', handleEvent);

return () => {
channel.off('message.new', handleEvent);
channel.off('message.updated', handleEvent);
channel.off('message.deleted', handleEvent);
channel.off('message.undeleted', handleEvent);
channel.off('channel.truncated', handleEvent);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [refreshUnreadCount, channelUpdateCount]);
}, [channel, refreshUnreadCount, channelUpdateCount]);

if (!Preview) return null;

const latestMessage = getLatestMessagePreview(channel, t, userLanguage);
const latestMessagePreview = getLatestMessagePreview(channel, t, userLanguage);

return (
<Preview
Expand All @@ -151,7 +167,8 @@ export const ChannelPreview = <
displayImage={displayImage}
displayTitle={displayTitle}
lastMessage={lastMessage}
latestMessage={latestMessage}
latestMessage={latestMessagePreview}
latestMessagePreview={latestMessagePreview}
messageDeliveryStatus={messageDeliveryStatus}
setActiveChannel={setActiveChannel}
unread={unread}
Expand Down
Loading
Loading