import { useChannelContext, useChatContext } from "stream-chat-react-native";
const Component = (props) => {
const { client } = useChatContext();
const { read, channel, channelUnreadState } = useChannelContext();
// 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
};
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 Native SDK manages the channel’s read state specifically for UI components within the Channel
component using a separate state variable called channelUnreadState
. This state is primarily used to display the unread message count in components like InlineUnreadIndicator
and UnreadMessagesNotification
, as well as custom components requiring similar functionality. A unique aspect of channelUnreadState
is that when a channel is opened and marked as read, it does not immediately reflect this update. This behavior ensures the user can see the number of unread messages remaining from their 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 ChannelContext
as channelUnreadState
. The state format is as follows:
Property | Type | Description |
---|---|---|
last_read | Date | Date when the channel was marked read the last time. |
unread_messages | number | The count of unread messages in a given channel. Unread count refers only to foreign (not own) unread messages. |
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. |
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:
Property | Type | Description |
---|---|---|
last_read | Date | Date 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_messages | number | The 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. |
user | user | Data 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_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. |
Access the read state
In the SDK, the read
and channelUnreadState
can be accessed via useChannelContext
consumer:
Mark a channel read
Channel can be marked read using the markRead()
function provided via ChannelContext
:
import { Button } from "react-native";
import { useChannelContext } from "stream-chat-react-native";
const MarkReadButton = (props) => {
const { markRead } = useChannelContext();
return (
<Button {...props} onClick={() => markRead()}>
Mark read
</Button>
);
};
The function accepts a single options
parameter of the following format:
Field | Type | Optional | Description |
---|---|---|---|
updateChannelUnreadState | Boolean | Yes | Signal, whether the channelUnreadState 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 updateChannelUnreadState 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:
Component | Description |
---|---|
Channel | Can be configured to mark active channel read when mounted. This can be done through its prop markReadOnMount . By default enabled. |
MessageList | Marks channel read when message list is scrolled to the bottom. |
UnreadMessagesNotification | Floating notification rendered in the message list. Contains a button, which when clicked, takes me to the first unread message. |
The message actions in the menu already has a action added to mark a message as unread and that will be reflected on the Channel state. You can go through our Customizing message actions guide.
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:
- User enters a channel with unread messages if
Channel
propmarkReadOnMount
is enabled (default behavior). - User scrolls up and back down to the latest message.
- User clicks the button on the default
UnreadMessagesNotification
component to jump to the first unread message. - The
ScrollToBottomButton
would appears that can take you to the latest message on click.
The channel is marked unread in the following scenarios:
- User with
read-events
permission is shownMark as unread
option in the message actions list.
The component InlineUnreadIndicator
is shown immediately below the last read message. It can be followed by own message or a message posted by another user.
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 | markReadOnMount (by default enabled) |
Customization through custom components
We can override the following components via passing a custom UI component to Channel
component:
Custom InlineUnreadIndicator
component
Will be rendered before the first unread message.
import { Channel } from "stream-chat-react-native";
const InlineUnreadIndicator = () => {
//... custom implementation
};
const Component = ({ children }) => (
<Channel InlineUnreadIndicator={InlineUnreadIndicator}>{children}</Channel>
);
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-native";
const UnreadMessagesNotification = (props: UnreadMessagesNotificationProps) => {
//... custom implementation
};
const Component = ({ children }) => (
<Channel UnreadMessagesNotification={UnreadMessagesNotification}>
{children}
</Channel>
);
The component can be configured through the following props:
Prop | Description | Type | Default |
---|---|---|---|
onPressHandler | Function callback that is trigged when the component is pressed. | Function |undefined | undefined |
onCloseHandler | Function callback that is trigged when the close icon in the component is pressed. | Function |undefined | undefined |
Custom ScrollToBottomButton
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 scroll to bottom button component.
import { Channel, ScrollToBottomButtonProps } from "stream-chat-react-native";
const ScrollToBottomButton = (props: ScrollToBottomButtonProps) => {
//... custom implementation
};
const Component = ({ children }) => (
<Channel ScrollToBottomButton={ScrollToBottomButton}>{children}</Channel>
);
Jumping to the first unread message
Initial scroll to first unread messages on mount
The behaviour can be enabled by enabling the prop initialScrollToFirstUnreadMessage
in the Channel
component.
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 loadChannelAtFirstUnreadMessage()
function to implement custom UI to jump to the first unread message. The function taks parameters like limit
, channelUnreadState
, setChannelUnreadState
, setTargetedMessage
. The channelUnreadState
, setChannelUnreadState
, setTargetedMessage
can be used from useChannelContext
.
Showing total unread counts in the app
To show the total un-read counts in your app, when you connect to a client, you can do the following:
- You get the total un-read count in the response of the
client.connectUser()
call and you can use the response to store the total count.
const [unreadCount, setUnreadCount] = useState<number>();
const client = StreamChat.getInstance<StreamChatGenerics>("Your API Key", {
timeout: 6000,
});
const user = {
id: "Your User ID",
};
const connectedUser = await client.connectUser(user, "Your User Token");
const initialUnreadCount = connectedUser?.me?.total_unread_count; // Use it to show the total unread count on the initial load.
- To listen to the total un-read count changes as different actions/events are performed/received in the SDK, you can rely on the client event listener:
useEffect(() => {
const listener = client?.on((e) => {
if (e.total_unread_count !== undefined) {
setUnreadCount(e.total_unread_count);
} else {
const countUnread = Object.values(chatClient.activeChannels).reduce(
(count, channel) => count + channel.countUnread(),
0,
);
setUnreadCount(countUnread);
}
});
return () => {
if (listener) {
listener.unsubscribe();
}
};
}, [chatClient]);