# 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`](/chat/docs/sdk/react-native/v8/contexts/channel-context/#read):

```tsx
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
};
```

### Mark a channel read

Channel can be marked read using the [`markRead()`](/chat/docs/sdk/react-native/v8/contexts/channel-context/#markread) function provided via `ChannelContext`:

```tsx
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. |

<admonition type="tip">

Prefer `markRead()` within `Channel` context; it throttles API calls to avoid mark-read rate limits.

</admonition>

## Default components involved in handling read state

The default components included in **marking a channel read**:

| Component                                                                                                                                                               | Description                                                                                                                          |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| [`Channel`](/chat/docs/sdk/react-native/v8/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`](/chat/docs/sdk/react-native/v8/ui-components/message-list/)                                                                                             | Marks channel read when message list is scrolled to the bottom.                                                                      |
| [`UnreadMessagesNotification`](https://github.com/GetStream/stream-chat-react-native/v8/tree/develop/package/src/components/MessageList/UnreadMessagesNotification.tsx) | 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](/chat/docs/sdk/react-native/v8/guides/customize-message-actions/).

The default components reflecting channel unread count:

| Component                                                                                                                                                               |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`PreviewUnreadCount`](/chat/docs/sdk/react-native/v8/core-components/channel-list/#previewunreadcount)                                                                 |
| [`InlineUnreadIndicator`](https://github.com/GetStream/stream-chat-react-native/v8/blob/develop/package/src/components/MessageList/InlineUnreadIndicator.tsx)           |
| [`UnreadMessagesNotification`](https://github.com/GetStream/stream-chat-react-native/v8/blob/develop/package/src/components/MessageList/UnreadMessagesNotification.tsx) |
| [`ScrollToBottomButton`](https://github.com/GetStream/stream-chat-react-native/v8/blob/develop/package/src/components/MessageList/ScrollToBottomButton.tsx)             |

<admonition type="note">

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.

</admonition>

## 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 jump to the first unread message.
4. The `ScrollToBottomButton` appears and can take you to the latest message.

The channel is marked unread in the following scenarios:

1. User with `read-events` permission is shown `Mark as unread` option 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`](/chat/docs/sdk/react-native/v8/core-components/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.

```tsx
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.

```tsx
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`](https://github.com/GetStream/stream-chat-react-native/v8/blob/develop/package/src/components/MessageList/ScrollToBottomButton.tsx), which shows unread count since the user scrolled away from the newest messages.

We can implement our own message scroll to bottom button component.

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

1. Read the total unread count from the `client.connectUser()` response.

```tsx
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.
```

2. Listen for updates via the client event listener:

```tsx
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]);
```


---

This page was last updated at 2026-04-17T17:33:45.512Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v8/guides/channel-read-state/](https://getstream.io/chat/docs/sdk/react-native/v8/guides/channel-read-state/).