Skip to content

Commit

Permalink
fix: get correct lastMessage and latestMessagePreview from channel st…
Browse files Browse the repository at this point in the history
…ate (#2535)
  • Loading branch information
MartinCupela authored Oct 15, 2024
1 parent c852fe4 commit ba6b1a3
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 46 deletions.
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

0 comments on commit ba6b1a3

Please sign in to comment.