Channel Read State

This guide intends to provide an overview how channel read state is handled by default in the SDK and how to customize this behavior to our liking.

The model

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

The state is maintained by Channel component and shared with its children via ChannelStateContext as channelUnreadUiState. The state format is as follows:

PropertyTypeDescription
last_readDateDate when the channel was marked read the last time.
unread_messagesnumberThe count of unread messages in a given channel. Unread count refers only to foreign (not own) unread messages.
first_unread_message_idstring or undefinedThe 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_idstring or undefinedThe ID of the message preceding the first unread message.

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, message.new. Each value of the read state object has then the following structure:

PropertyTypeDescription
last_readDateDate when the channel was marked read the last time. The value is provided with ChannelResponse when querying channels or on notification.mark_unread event.
unread_messagesnumberThe count of unread messages in a given channel for a given user. Unread count refers only to foreign (not own) unread messages. The value is provided with ChannelResponse when querying channels or on notification.mark_unread event.
useruserData of a user, whose read state is described in this object. The value is provided with ChannelResponse when querying channels or on notification.mark_unread event.
first_unread_message_idstring or undefinedThe 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_idstring or undefinedThe ID of the message preceding the first unread message. The value is provided with ChannelResponse when querying channels or on notification.mark_unread event.

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:

import { useChannelStateContext, useChatContext } from 'stream-chat-react';

const Component = (props) => {
    const { client } = useChatContext();
    const { read, channel, channelUnreadUiState } = useChannelStateContext();

    // channel read state for some user
    const channelReadStateForAUser = read[props.user.id];

    // channel read state for own user
    const channelReadStateForMyUser = client.user && read[client.user.id];

    // easier way to access own user's unread count for a given channel
    const unreadCount = channel.unreadCount();

    //... code
};

Mark a channel read

Channel can be marked read using the markRead() function provided via ChannelActionContext:

import { useChannelActionContext } from 'stream-chat-react';

const MarkReadButton = (props) => {
    const { markRead } = useChannelActionContext();

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

The function accepts a single options parameter of the following format:

FieldTypeOptionalDescription
updateChannelUiUnreadStatebooleanyesSignal, 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.

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.

Default components involved in handling read state

The default components included in marking a channel read:

ComponentDescription
ChannelCan be configured to mark active channel read when mounted. This can be done through its prop markReadOnMount. By default enabled.
MessageList, VirtualizedMessageListMarks channel read when message list is scrolled to the bottom.
UnreadMessagesNotificationFloating 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:

ComponentDescription
MessageActionsBoxAction Mark as Unread is displayed if a user has read-events permission and the actions are displayed for a non-thread message posted by another user. Message which id matches that of the current user’s read state’s first_unread_message_id is not shown option Mark as Unread.

The default components reflecting channel unread count:

Message threads do not participate in handling read state of a channel. Thread replies are not observed for unread count. Therefore, none of the UI components related to read state are rendered in threads.

Default channel read state handling

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 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:

  • 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:

ComponentProp
ChannelmarkReadOnMount (by default enabled)

Customization through custom components

We can override the following components via Channel’s component context:

Custom UnreadMessagesSeparator component

Will be rendered before the first unread message.

import {
    Channel,
    UnreadMessagesSeparatorProps,
} from 'stream-chat-react';

const UnreadMessagesSeparator = (props: UnreadMessagesSeparatorProps) => {
    //... custom implementation
}
const Component = ({children}) => (
    <Channel UnreadMessagesSeparator={UnreadMessagesSeparator}>
        {children}
    </Channel>
);

The component can be configured through the following props:

PropDescriptionTypeDefault
showCountConfiguration parameter to determine, whether the unread count is to be shown on the component.booleanfalse
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 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).

import {
    Channel,
    UnreadMessagesNotificationProps,
} from 'stream-chat-react';

const UnreadMessagesNotification = (props: UnreadMessagesNotificationProps) => {
    //... custom implementation
}
const Component = ({children}) => (
    <Channel UnreadMessagesNotification={UnreadMessagesNotification}>
        {children}
    </Channel>
);

The component can be configured through the following props:

PropDescriptionTypeDefault
showCountConfiguration parameter to determine, whether the unread count is to be shown on the component.booleanfalse
queryMessageLimitConfiguration parameter to determine the message page size, when jumping to the first unread message.number100
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 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.

import {
    MessageNotificationProps,
    Channel,
} from 'stream-chat-react';

const MessageNotification = (props: MessageNotificationProps) => {
    //... custom implementation
};

const Component = ({children}) => (
    <Channel MessageNotification={MessageNotification} >
        {children}
    </Channel>
);

Jumping to the first unread message

Default SDK component to jump to the first unread message

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

Use jumpToFirstUnreadMessage() function to implement custom UI to jump to the first unread message. The function takes one parameter queryMessageLimit that specifies the message page size if the message has to be loaded from the back-end API.

© Getstream.io, Inc. All Rights Reserved.