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 explains how the SDK handles channel read state by default and how to customize it.
Best Practices
- Use
markRead()from context to keep UI and backend state aligned. - Avoid overriding unread UI unless you preserve the same behavior.
- Keep unread indicators consistent across channel list and message list.
- Respect throttling to avoid read-rate limits.
- Remember threads don’t affect channel unread counts by default.
The model
The SDK keeps a separate channelUnreadState inside Channel for UI components like InlineUnreadIndicator and UnreadMessagesNotification. When you open a channel and mark it read, channelUnreadState doesn’t immediately reset so users can still see their previous unread count.
The backend read state is available via channel.state.read.
Channel UI unread state
Channel maintains this state and shares it via ChannelContext as channelUnreadState.
| 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 comes from the channel query response (ChannelResponse.read) and is transformed from an array into an object keyed by user ID. It updates on WS events like message.read, notification.mark_unread, and message.new.
| 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
Access read and channelUnreadState via useChannelContext:
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} onPress={() => markRead()} title="Mark read" />;
};markRead accepts an optional options parameter:
| 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. |
Prefer markRead() within Channel context; it throttles API calls to avoid mark-read rate limits.
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 menu includes a “mark as unread” action that updates channel state. See Customizing message actions.
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
ChannelpropmarkReadOnMountis enabled (default behavior). - User scrolls up and back down to the latest message.
- User clicks the button on the default
UnreadMessagesNotificationcomponent to jump to the first unread message. - The
ScrollToBottomButtonappears and can take you to the latest message.
The channel is marked unread in the following scenarios:
- User with
read-eventspermission is shownMark as unreadoption in the message actions list.
InlineUnreadIndicator appears below the last read message. It can be followed by your own or another user’s message.
Channel read state handling customization
Customization through component props
You can configure when a channel is marked read by tweaking these default component props:
| Component | Prop |
|---|---|
Channel | markReadOnMount (by default enabled) |
Customization through custom components
You can override these components by passing custom UI components to Channel:
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
Rendered only when UnreadMessagesSeparator is not visible. The default implementation is a floating element that shows unread count since the user scrolled away from the latest message.
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 | Called when the component is pressed. | Function|undefined | undefined |
onCloseHandler | Called when the close icon is pressed. | Function|undefined | undefined |
Custom ScrollToBottomButton component
The SDK exports ScrollToBottomButton, which shows unread count since the user scrolled away from the newest messages.
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
Enable initialScrollToFirstUnreadMessage on Channel.
Default SDK component to jump to the first unread message
UnreadMessagesNotification can scroll to the first unread message. If it’s not loaded, it’s fetched from the API.
API to jump to the first unread message
Use loadChannelAtFirstUnreadMessage() to implement custom UI. It takes limit, channelUnreadState, setChannelUnreadState, and setTargetedMessage, all available from useChannelContext.
Showing total unread counts in the app
To show total unread counts:
- Read the total unread count from the
client.connectUser()response.
const [unreadCount, setUnreadCount] = useState<number>();
const client = StreamChat.getInstance("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 on initial load.- Listen for updates via 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]);