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: mark channel read on scroll to bottom of the main message list #2283

Merged
merged 12 commits into from
Feb 21, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ A custom function to provide size configuration for image attachments

| Type |
| ---------------------------------------------------------------- |
| `(a: Attachment, e: HTMLElement) => ImageAttachmentConfigration` |
| `(a: Attachment, e: HTMLElement) => ImageAttachmentConfiguration` |

### hasMore

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Custom UI component to display a attachment previews in `MessageInput`.

| Type | Default |
| --------- | ----------------------------------------------------------------------------------- |
| component | <GHComponentLink text='Attachment' path='/MessageInput/AttachmentPreviewList.tsx'/> |
| component | <GHComponentLink text='AttachmentPreviewList' path='/MessageInput/AttachmentPreviewList.tsx'/> |

### AutocompleteSuggestionHeader

Expand Down
12 changes: 6 additions & 6 deletions docusaurus/docs/React/components/core-components/channel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ A list of accepted file upload types.

### activeUnreadHandler

Custom handler function that runs when the active channel has unread messages (i.e., when chat is running on a separate browser tab).
Custom handler function that runs when the active channel has unread messages and the app is running on a separate browser tab.

| Type |
| ----------------------------------------------- |
Expand All @@ -138,11 +138,11 @@ Custom UI component to display a message attachment.

### AttachmentPreviewList

Custom UI component to display a attachment previews in `MessageInput`.
Custom UI component to display an attachment previews in `MessageInput`.

| Type | Default |
| --------- | ----------------------------------------------------------------------------------- |
| component | <GHComponentLink text='Attachment' path='/MessageInput/AttachmentPreviewList.tsx'/> |
| component | <GHComponentLink text='AttachmentPreviewList' path='/MessageInput/AttachmentPreviewList.tsx'/> |

### AutocompleteSuggestionHeader

Expand Down Expand Up @@ -422,7 +422,7 @@ A custom function to provide size configuration for image attachments

| Type |
| ---------------------------------------------------------------- |
| `(a: Attachment, e: HTMLElement) => ImageAttachmentConfigration` |
| `(a: Attachment, e: HTMLElement) => ImageAttachmentConfiguration` |

### initializeOnMount

Expand Down Expand Up @@ -481,7 +481,7 @@ Configuration parameter to mark the active channel as read when mounted (opened)

| Type | Default |
| ------- | ------- |
| boolean | false |
| boolean | true |

### Input

Expand All @@ -497,7 +497,7 @@ Custom component to render link previews in `MessageInput`.

| Type | Default |
| --------- | ----------------------------------------------------------------------------------- |
| component | <GHComponentLink text='MessageInputFlat' path='/MessageInput/LinkPreviewList.tsx'/> |
| component | <GHComponentLink text='LinkPreviewList' path='/MessageInput/LinkPreviewList.tsx'/> |

### LoadingErrorIndicator

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,14 +459,6 @@ Function called when more messages are to be loaded, provide your own function t
| -------- | ---------------------------------------------------------------------------------------- |
| function | [ChannelActionContextValue['loadMore']](../contexts/channel-action-context.mdx#loadmore) |

### markReadOnScrolledToBottom

When enabled, the channel will be marked read when a user scrolls to the bottom. Ignored when scrolled to the bottom of a thread message list.

| Type | Default |
|---------|---------|
| boolean | false |

### Message

Custom UI component to display an individual message.
Expand Down Expand Up @@ -623,6 +615,15 @@ channel, the `MessageNotification` component displays when new messages arrive.
| ------ | ------- |
| number | 200 |

### showUnreadNotificationAlways

The floating notification informing about unread messages will be shown when the `UnreadMessagesSeparator` is not visible. The default is false, that means the notification
is shown only when viewing unread messages.

| Type | Default |
|---------|---------|
| boolean | false |

### threadList

If true, indicates that the current `MessageList` component is part of a `Thread`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,6 @@ Function called when more messages are to be loaded, provide your own function t
| -------- | ---------------------------------------------------------------------------------------- |
| function | [ChannelActionContextValue['loadMore']](../contexts/channel-action-context.mdx#loadmore) |

### markReadOnScrolledToBottom

When enabled, the channel will be marked read when a user scrolls to the bottom. Ignored when scrolled to the bottom of a thread message list.

| Type | Default |
|---------|---------|
| boolean | false |

### Message

Custom UI component to display an individual message.
Expand Down Expand Up @@ -254,6 +246,15 @@ If true, the Giphy preview will render as a separate component above the `Messag
| ------- | ------- |
| boolean | false |

### showUnreadNotificationAlways

The floating notification informing about unread messages will be shown when the `UnreadMessagesSeparator` is not visible. The default is false, that means the notification
is shown only when viewing unread messages.

| Type | Default |
|---------|---------|
| boolean | false |

### stickToBottomScrollBehavior

The scroll-to behavior when new messages appear. Use `'smooth'` for regular chat channels and `'auto'`
Expand Down
75 changes: 59 additions & 16 deletions docusaurus/docs/React/guides/channel-read-state.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ 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` component 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`. This state is dedicated to show unread count on components `UnreadMessagesSeparator` and `UnreadMessagesNotification` (or other custom components that need its behavior). The `channelUnreadUiState` is special in that when a channel is opened and marked read, the `channelUnreadUiState` does not reflect this initial update. This is in order the user can see, how many unread messages there have been left since the previous session.

Channel read state reflecting the current back-end state can be accessed via `channel.state.read` mapping.

### Channel UI unread state

Expand All @@ -25,7 +27,7 @@ The state is maintained by `Channel` component and shared with its children via

### Channel read state for all users

The read state is extracted from the channel query response, specifically from each `ChannelResponse` object's `read` attribute This is internally transformed from an array of users' read statuses into and object indexed by user id. The read state is updated upon receiving WS events like `message.read`, `notification.mark_unread`. Each value of the `read` state object has then the following structure:
The read state is extracted from the channel query response, specifically from each `ChannelResponse` object's `read` attribute. This is internally transformed from an array of users' read statuses into and object indexed by user id. The read state is updated upon receiving WS events like `message.read`, `notification.mark_unread`, `message.new`. Each value of the `read` state object has then the following structure:

| Property | Type | Description |
|-----------------------------|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
Expand All @@ -35,6 +37,10 @@ The read state is extracted from the channel query response, specifically from e
| **first_unread_message_id** | `string` or `undefined` | The ID of the message that was marked unread (`notification.mark_unread` event). The value is available only when a message is marked unread. Therefore, cannot be relied on to place unread messages UI. |
| **last_read_message_id** | `string` or `undefined` | The ID of the message preceding the first unread message. The value is provided with `ChannelResponse` when querying channels or on `notification.mark_unread` event. |

:::note
Be aware that only the last 100 newest messages can be marked unread. If older messages are marked unread, an error notification is shown informing about this limitation.
:::

### Access the read state

In the SDK, the `read` and `channelUnreadUiState` can be accessed via [`useChannelStateContext` consumer](../../components/contexts/channel_state_context#read):
Expand Down Expand Up @@ -78,7 +84,6 @@ The function accepts a single `options` parameter of the following format:
| Field | Type | Optional | Description |
|------------------------|-----------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `updateChannelUiUnreadState` | `boolean` | yes | Signal, whether the `channelUnreadUiState` should be updated. The local state update is prevented when the Channel component is mounted. This is in order to keep the UI indicating the original unread state, when the user opens a channel. If the value for `updateChannelUiUnreadState` is not provided, the state is updated. |
|

:::important
Please, prefer using the `markRead()` function everywhere in the `Channel` context as this function throttles the API calls thus preventing you from hitting the API limit of mark-read calls.
Expand All @@ -90,8 +95,8 @@ 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. |
| [`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. |
| [`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 enabled. |
| [`MessageList`](../../components/core-components/message_list), [`VirtualizedMessageList`](../../components/core-components/virtualized_list) | Marks channel read when message list is scrolled to the bottom. |
| [`UnreadMessagesNotification`](../../components/contexts/component_context#unreadmessagesnotification) | Floating notification rendered in the message list. Contains a button, which when clicked, marks the channel read. |

The default components included in **marking a channel unread**:
Expand All @@ -118,26 +123,26 @@ 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 if `Channel` prop `markReadOnMount` is enabled (default behavior).
2. User clicks the button on the default `UnreadMessagesNotification` component to mark channel read.
2. User scrolls up and back down to the latest message.
3. User clicks the button on the default `UnreadMessagesNotification` component to mark channel read.

The channel is marked unread in the following scenarios:

1. User with `read-events` permission selects `Mark as unread` option from the `MessageActionsBox`.

The component `UnreadMessagesSeparator` is shown immediately below the last read message. It can be followed by own message or a message posted by another user. It does not show unread count if among the unread messages are only own messages (own message can be marked unread).
The component `UnreadMessagesSeparator` is shown immediately below the last read message. It can be followed by own message or a message posted by another user. It does not show unread count if:
- `showCount` prop is enabled and among the unread messages are only own messages (own message can be marked unread).
- `showCount` prop is disabled (default)

## Channel read state handling customization

### Customization through component props

There is a possibility to configure when a channel is marked read by tweaking these default components' props:

| Component | Prop |
|-----------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|
| [`Channel`](../../components/core-components/channel) | `markReadOnMount` (by default disabled) |
| [`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 `Channel` component mount, the channel can be marked read when scrolled to the bottom.
| Component | Prop |
|-------------------------------------------------------|----------------------------------------|
| [`Channel`](../../components/core-components/channel) | `markReadOnMount` (by default enabled) |

### Customization through custom components

Expand All @@ -163,9 +168,26 @@ const Component = ({children}) => (
);
```

The component can be configured through the following props:

| Prop | Description | Type | Default |
|---------------------|-------------------------------------------------------------------------------------------------------|-----------|---------|
| `showCount` | Configuration parameter to determine, whether the unread count is to be shown on the component. | `boolean` | `false` |

```tsx
import {
UnreadMessagesSeparator as StreamUnreadMessagesSeparator,
UnreadMessagesSeparatorProps,
} from 'stream-chat-react';

const UnreadMessagesSeparator = (props: UnreadMessagesSeparatorProps) => {
return <StreamUnreadMessagesSeparator {...props} showCount />
}
```

#### Custom `UnreadMessagesNotification` component

Will be rendered only when being scrolled to unread messages in a message list. The default implementation positions the notification as a floating element above the messages in a message list.
Will be rendered only when `UnreadMessagesSeparator` is not visible in message list. The default implementation positions the notification as a floating element above the messages in a message list. It shows the number of unread messages since the user has scrolled away from the latest message (bottom of the message list).

```tsx
import {
Expand All @@ -183,9 +205,30 @@ const Component = ({children}) => (
);
```

The component can be configured through the following props:

| Prop | Description | Type | Default |
|---------------------|-------------------------------------------------------------------------------------------------------|-----------|---------|
| `showCount` | Configuration parameter to determine, whether the unread count is to be shown on the component. | `boolean` | `false` |
| `queryMessageLimit` | Configuration parameter to determine the message page size, when jumping to the first unread message. | `number` | `100` |


```tsx
import {
UnreadMessagesNotification as StreamUnreadMessagesNotification,
UnreadMessagesNotificationProps,
} from 'stream-chat-react';

const UnreadMessagesNotification = (props: UnreadMessagesNotificationProps) => {
return <StreamUnreadMessagesNotification {...props} queryMessageLimit={50} showCount />
}
```

#### Custom `MessageNotification` component

The SDK exports [`ScrollToBottomButton`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageList/ScrollToBottomButton.tsx) that shows the unread count and is rendered only when scrolled away from the bottom of the message list. We can however implement our own message notification component.
The SDK exports [`ScrollToBottomButton`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageList/ScrollToBottomButton.tsx) that shows the unread count since the point the user has scrolled away from the newest messages in the list.

We can implement our own message notification component.

```tsx
import {
Expand All @@ -208,7 +251,7 @@ const Component = ({children}) => (

### Default SDK component to jump to the first unread message

The SDK provides a component `UnreadMessagesNotification`, that when clicked on the part `X Unread messages`, the message list scrolls to the first unread message. If the first unread message is not loaded in the local channel state, the message is retrieved from the API.
The SDK provides a component `UnreadMessagesNotification`, that when clicked on the part `Unread messages`, the message list scrolls to the first unread message. If the first unread message is not loaded in the local channel state, the message is retrieved from the API.


### API to jump to the first unread message
Expand Down
Loading
Loading