Skip to content

Commit

Permalink
fix: remove mark read functionality from ChannelPreview (#2273)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinCupela committed Feb 13, 2024
1 parent 8f490fa commit 3be1ec5
Show file tree
Hide file tree
Showing 5 changed files with 12 additions and 472 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,22 +109,6 @@ Latest message preview to display. Will be either a string or a JSX.Element rend
| --------------------- |
| string \| JSX.Element |

### markActiveChannelReadOn

Configuration that determines when and whether a channel is marked read upon clicking ChannelPreview.
1. `MarkChannelReadOn.never`: Prevents a channel from being marked read when any ChannelPreview is clicked.
2. `MarkChannelReadOn.leave`: Marks active channel read before switching the active channel state to the clicked preview's channel.
This means that active channel being left is marked read before navigating to another channel.
3. `MarkChannelReadOn.reenter`: Channel, which is re-entered by clicking the preview, is marked read. That means:
- an active channel can be marked unread, left and on return marked read.
- a non-active channel receives a new message, is entered, left and re-entered and marked read.

The default configuration is set to `MarkChannelReadOn.reenter`.

| Type | Default |
|------|-----------------------------|
| enum | `MarkChannelReadOn.reenter` |

### messageDeliveryStatus

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. The value is calculated from `channel.read` data on mount and updated on every `message.new` resp. `message.read` WS event.
Expand Down
30 changes: 5 additions & 25 deletions docusaurus/docs/React/guides/channel-read-state.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This guide intends to provide an overview how channel read state is handled by d

## The model

The React SDK maintains channel read state for UI components inside `Channel` componet in a separate variable `channelUnreadUiState`. Channel read state reflecting the back-end state, can be accessed via `channel.state.read` mapping.
The React SDK maintains channel read state for UI components inside `Channel` component in a separate variable `channelUnreadUiState`. Channel read state reflecting the back-end state, can be accessed via `channel.state.read` mapping.

### Channel UI unread state

Expand Down Expand Up @@ -69,7 +69,7 @@ import { useChannelActionContext } from 'stream-chat-react';
const MarkReadButton = (props) => {
const { markRead } = useChannelActionContext();

return <button {...props} onClick={markRead}>Mark read</button>
return <button {...props} onClick={() => markRead()}>Mark read</button>
};
```

Expand All @@ -91,7 +91,6 @@ The default components included in **marking a channel read**:
| Component | Description |
|-----------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [`Channel`](../../components/core-components/channel) | Can be configured to mark active channel read when mounted. This can be done through its prop `markReadOnMount`. By default disabled. |
| [`ChannelPreviewMessenger`](../../components/utility-components/channel_preview_ui) | Can be configured to mark active channel read when re-entered or left or disabled. This can be done through the prop `markActiveChannelReadOn`. By default channel is marked read on re-enter. |
| [`MessageList`](../../components/core-components/message_list), [`VirtualizedMessageList`](../../components/core-components/virtualized_list) | Can be configured to mark channel read when message list is scrolled to the bottom. This can be done through the list's prop `markReadOnScrolledToBottom`. By default disabled. |
| [`UnreadMessagesNotification`](../../components/contexts/component_context#unreadmessagesnotification) | Floating notification rendered in the message list. Contains a button, which when clicked, marks the channel read. |

Expand All @@ -118,7 +117,7 @@ Message threads do not participate in handling read state of a channel. Thread r

The channel is marked read in the following scenarios:

1. User enters a channel with unread messages, leaves and when returns back the channel is marked read.
1. User enters a channel with unread messages if `Channel` prop `markReadOnMount` is enabled (default behavior).
2. User clicks the button on the default `UnreadMessagesNotification` component to mark channel read.

The channel is marked unread in the following scenarios:
Expand All @@ -136,32 +135,13 @@ There is a possibility to configure when a channel is marked read by tweaking th
| Component | Prop |
|-----------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|
| [`Channel`](../../components/core-components/channel) | `markReadOnMount` (by default disabled) |
| [`ChannelPreviewMessenger`](../../components/utility-components/channel_preview_ui) | `markActiveChannelReadOn` (`MarkActiveChannelOn.reenter`) |
| [`MessageList`](../../components/core-components/message_list), [`VirtualizedMessageList`](../../components/core-components/virtualized_list) | `markReadOnScrolledToBottom` (by default disabled) |

And so instead of marking a channel read on re-enter or when leaving the given channel, the channel can be marked read when scrolled to the bottom or immediately when mounted.

To disable marking a channel read when `ChannelPreview` is clicked, we need to provide a custom `Preview` component that renders the default:

```tsx
import {
ChannelList,
ChannelPreviewMessenger,
ChannelPreviewUIComponentProps,
MarkActiveChannelReadOn,
} from 'stream-chat-react';

const Preview = (props: ChannelPreviewUIComponentProps) =>
<ChannelPreviewMessenger {...props} markActiveChannelReadOn={MarkActiveChannelReadOn.never}/>

const Sidebar = () => (
<ChannelList filters={filters} sort={sort} options={options} Preview={Preview}/>
);
```
And so instead of marking a channel read on `Channel` component mount, the channel can be marked read when scrolled to the bottom.

### Customization through custom components

Aside from the custom `Preview` component, we can override the following components via `Channel`'s component context:
We can override the following components via `Channel`'s component context:

#### Custom `UnreadMessagesSeparator` component

Expand Down
17 changes: 1 addition & 16 deletions src/components/ChannelPreview/ChannelPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react';

import { ChannelPreviewMessenger, MarkChannelReadOn } from './ChannelPreviewMessenger';
import { ChannelPreviewMessenger } from './ChannelPreviewMessenger';
import { useIsChannelMuted } from './hooks/useIsChannelMuted';
import { useChannelPreviewInfo } from './hooks/useChannelPreviewInfo';
import { getLatestMessagePreview } from './utils';
Expand Down Expand Up @@ -30,21 +30,6 @@ export type ChannelPreviewUIComponentProps<
lastMessage?: StreamMessage<StreamChatGenerics>;
/** Latest message preview to display, will be a string or JSX element supporting markdown. */
latestMessage?: string | JSX.Element;
/**
* Configuration that determines when and whether a channel is marked read upon clicking ChannelPreview.
* 1. MarkChannelReadOn.never:
* Prevents a channel from being marked read when any ChannelPreview is clicked.
* 2. MarkChannelReadOn.leave:
* Marks active channel read before switching the active channel state to the clicked preview's channel.
* This means that active channel being left is marked read before navigating to another channel.
* 3. MarkChannelReadOn.reenter:
* Channel, which is re-entered by clicking the preview, is marked read.
* That means
* 1. an active channel can be marked unread, left and on return marked read.
* 2. a non-active channel receives a new message, is entered, left and re-entered and marked read.
* The default configuration is set to MarkChannelReadOn.reenter.
*/
markActiveChannelReadOn?: MarkChannelReadOn;
/** 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 Down
63 changes: 2 additions & 61 deletions src/components/ChannelPreview/ChannelPreviewMessenger.tsx
Original file line number Diff line number Diff line change
@@ -1,105 +1,46 @@
import React, { useEffect, useRef } from 'react';
import React, { useRef } from 'react';
import clsx from 'clsx';

import { Avatar as DefaultAvatar } from '../Avatar';
import { useChatContext } from '../../context';

import type { Event } from 'stream-chat';
import type { ChannelPreviewUIComponentProps } from './ChannelPreview';

import type { DefaultStreamChatGenerics } from '../../types/types';

export enum MarkChannelReadOn {
'leave',
'reenter',
}

const UnMemoizedChannelPreviewMessenger = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
>(
props: ChannelPreviewUIComponentProps<StreamChatGenerics>,
) => {
const {
active,
activeChannel,
Avatar = DefaultAvatar,
channel,
className: customClassName = '',
displayImage,
displayTitle,
latestMessage,
markActiveChannelReadOn,
onSelect: customOnSelectChannel,
setActiveChannel,
unread,
watchers,
} = props;

const { client } = useChatContext<StreamChatGenerics>('ChannelPreviewMessenger');
const channelPreviewButton = useRef<HTMLButtonElement | null>(null);

const avatarName =
displayTitle || channel.state.messages[channel.state.messages.length - 1]?.user?.id;

const previouslyClicked = useRef(active);

const handleMarkReadOnReenter = () => {
if (!client.user) return;
const ownReadState = channel.state.read[client.user.id];
if (!previouslyClicked.current) {
previouslyClicked.current = true;
} else if (
!active &&
(ownReadState?.first_unread_message_id || ownReadState?.unread_messages > 0) &&
previouslyClicked.current
) {
channel.markRead();
}
};

const handleMarkReadOnLeave = () => {
if (!(client.user && activeChannel)) return;
const ownReadState = activeChannel.state.read[client.user.id];
if (ownReadState?.first_unread_message_id || ownReadState?.unread_messages > 0) {
activeChannel.markRead();
}
};

const onSelectChannel = (e: React.MouseEvent<HTMLButtonElement>) => {
if (customOnSelectChannel) {
customOnSelectChannel(e);
} else if (setActiveChannel) {
// eslint-disable-next-line default-case
switch (markActiveChannelReadOn) {
case MarkChannelReadOn.leave:
handleMarkReadOnLeave();
break;
case MarkChannelReadOn.reenter:
handleMarkReadOnReenter();
break;
}
setActiveChannel(channel, watchers);
}
if (channelPreviewButton?.current) {
channelPreviewButton.current.blur();
}
};

useEffect(() => {
if (markActiveChannelReadOn !== MarkChannelReadOn.reenter) return;
const handleEvent = (event: Event) => {
if (active) return;
if (channel.cid !== event.cid) return;
if (event.type === 'notification.mark_unread' && event.user?.id !== client.user?.id) return;
previouslyClicked.current = false;
};
channel.on('notification.mark_unread', handleEvent);
channel.on('message.new', handleEvent);
return () => {
channel.off('notification.mark_unread', handleEvent);
channel.off('message.new', handleEvent);
};
}, [active, channel, client, markActiveChannelReadOn, previouslyClicked]);

return (
<button
aria-label={`Select Channel: ${displayTitle || ''}`}
Expand Down
Loading

0 comments on commit 3be1ec5

Please sign in to comment.