# Reading state in components

> **Info:** This is release candidate documentation for **Stream Chat React SDK v15**. For the latest stable version, see the [latest version (v14)](https://getstream.io/chat/docs/sdk/react/).

A component reads state with `useStateStore(store, selector)`. The hook subscribes to one store of a state-layer object, and re-renders the component when the values the selector picks change. What a store is, and which objects have one, is on [Reactive state](https://getstream.io/chat/docs/sdk/react/v15/state/state-and-entities/reactive-state/).

Here is an unread badge for the current channel:

```tsx
import type { ChannelStateData } from "stream-chat";
import { useChannel, useChatContext, useStateStore } from "stream-chat-react";

const readSelector = (state: ChannelStateData) => ({ read: state.read });

export const UnreadBadge = () => {
  const { client } = useChatContext();
  const channel = useChannel();
  const { read } = useStateStore(channel.state, readSelector);

  const unread = client.userID
    ? (read[client.userID]?.unread_messages ?? 0)
    : 0;
  if (!unread) return null;

  return <span className="unread-badge">{unread}</span>;
};
```

1. `useChannel()` finds the channel: the `Channel`'s, or the thread's channel inside a `Thread`.
2. `readSelector` picks the `read` field from `channel.state`, which holds every member's read state.
3. `useStateStore` subscribes to `channel.state`. When the channel's state changes, it runs the selector again and re-renders `UnreadBadge` only if `read` is a different value.
4. The component works out the current user's count from `read` after selecting it.

## Which owner a hook reads

The context hooks find their owner from where the component sits, so the same component works in a channel and in a thread:

| Hook                             | Inside a `Channel`         | Inside a `Thread`         |
| -------------------------------- | -------------------------- | ------------------------- |
| `useChannel()`                   | the channel                | the thread's channel      |
| `useThreadContext()`             | `undefined`                | the thread                |
| `useMessageComposerController()` | `channel.messageComposer`  | `thread.messageComposer`  |
| `useMessagePaginator()`          | `channel.messagePaginator` | `thread.messagePaginator` |

Two more cases:

- **A composer you supply.** Below a `MessageComposerControllerProvider`, `useMessageComposerController()` returns the composer that provider was given, for example the one editing a message inline.
- **A poll.** Inside a poll in a message, `usePollContext()` returns `{ poll }`. Read its state from `poll.state`.

`useMessagePaginator()` picks the message list the way the SDK's own message lists do: the thread's when there is one, otherwise the channel's.

```tsx
import type { MessagePaginatorState } from "stream-chat";
import { useMessagePaginator, useStateStore } from "stream-chat-react";

const itemsSelector = (state: MessagePaginatorState) => ({
  items: state.items,
});

export const LoadedMessageCount = () => {
  const paginator = useMessagePaginator();
  const { items } = useStateStore(paginator.state, itemsSelector);

  return <span>{items?.length ?? 0} messages loaded</span>;
};
```

## The useStateStore rules

1. **Define selectors at module scope.** The hook resubscribes whenever it gets a different selector function. When a selector needs a prop, wrap it in `useCallback`.
2. **Return an object with fixed keys.** Write `(state) => ({ items: state.items })`. Never return the slice itself (`(state) => state.items`), and never a primitive (`(state) => state.isLoading`): the hook compares the returned object key by key.
3. **Derive values after selecting.** Select the fields as they are and compute from them in the component, as `UnreadBadge` does. A selector that maps or filters builds a new array on every run, so the component re-renders on every change to the store.
4. **Import `useStateStore` from `stream-chat-react`.** It is the one the SDK's components use.

The store may be `undefined`, for example before the object that holds it exists. The hook then returns `undefined`, so you don't need a conditional hook call.

A selector that depends on a prop:

```tsx
import { useCallback } from "react";
import type { ChannelStateData } from "stream-chat";
import { useChannel, useStateStore } from "stream-chat-react";

export const MemberRole = ({ userId }: { userId: string }) => {
  const channel = useChannel();
  const selector = useCallback(
    (state: ChannelStateData) => ({ member: state.members[userId] }),
    [userId],
  );
  const { member } = useStateStore(channel.state, selector);

  return <span>{member?.channel_role ?? "not a member"}</span>;
};
```

## Pitfalls

### Selector identity

An inline selector is a new function on every render, so the hook unsubscribes and subscribes again each time:

```tsx
import type { ChannelStateData } from "stream-chat";
import { useChannel, useStateStore } from "stream-chat-react";

// Resubscribes on every render.
export const TypingCountInline = () => {
  const channel = useChannel();
  const { typing } = useStateStore(
    channel.state,
    (state: ChannelStateData) => ({
      typing: state.typing,
    }),
  );
  return <span>{Object.keys(typing).length}</span>;
};

// Subscribes once.
const typingSelector = (state: ChannelStateData) => ({ typing: state.typing });

export const TypingCount = () => {
  const channel = useChannel();
  const { typing } = useStateStore(channel.state, typingSelector);
  return <span>{Object.keys(typing).length}</span>;
};
```

The same applies to the `filter` you pass to `useNotifications` and `useSystemNotifications`: the hook builds its selector from it, so an inline filter resubscribes on every render too. Define it at module scope.

### Selections that go stale

A selector that returns the slice itself compiles, but misses changes. Take `(state) => state.members`, an object keyed by user ID:

- **An added member is missed.** `useStateStore` compares the previous selection's keys, and the previous selection has no key for the new member.
- **A removed member is missed.** The store only notifies the hook when the new selection's keys differ, and the new selection no longer has the removed key.

`(state) => state.items` fails the same way: a message added at the end, or the last one removed, doesn't re-render the component. Wrap the slice in an object, `(state) => ({ members: state.members })`, and the comparison is on the slice as a whole.

### Two useStateStore hooks

`@stream-io/state-store`, the package the stores come from, has its own `useStateStore` in `@stream-io/state-store/react-bindings`. The two differ:

- The one in `stream-chat-react` compares the keys of the previous selection with `===`. The one in `@stream-io/state-store` compares the keys of the new selection with `Object.is`.
- Only the one in `stream-chat-react` supports server rendering.

With selectors that follow the rules above, both see the same changes. Import the one from `stream-chat-react`.

### Getters that aren't reactive

Many objects have getters that read a store's current value. Reading one during render subscribes to nothing: the component shows the new value only when something else re-renders it.

| Getter                                                   | Subscribe to                                                 |
| -------------------------------------------------------- | ------------------------------------------------------------ |
| `channel.data`                                           | `channel.state`, field `data`                                |
| `channel.state.unreadCount`                              | `channel.state`, field `read`, as in `UnreadBadge`           |
| `channel.state.members`, `.read`, `.typing`, `.watchers` | `channel.state`, the field of the same name                  |
| `thread.ownUnreadCount`                                  | `thread.state`, field `read` (`unreadMessageCount` per user) |
| `messagePaginator.isViewingLive`                         | `messagePaginator.liveViewState`                             |
| `client.user`                                            | no store holds the current user yet                          |

### Store names vary

Most stores are called `state`, but not all of them:

- `channel.state` is itself the channel's store. There is no `channel.state.state`.
- `client.notifications.store` holds the notifications.
- `channel.messageReceiptsTracker.snapshotStore` holds delivery and read receipts.
- `configState` holds an object's settings, on the channel, the composer, the paginators and the other configurable objects.
- A message paginator has extra stores next to `state`: `unreadStateSnapshot` (the unread boundary), `liveViewState`, `aggregateState` (the latest message) and `messageFocusSignal` (the message to scroll to and highlight).
- The thread list is `client.threads.paginator.state`. `client.threads.state` holds the unread thread count and the unseen threads.

The fields of each store are in [Reference → State](https://getstream.io/chat/docs/sdk/react/v15/reference/state/channel-and-channel-state/).

### Context hooks that throw

`useChannel()` throws outside a `Channel` or a `Thread`, and so do the hooks built on it, such as `useMessageComposerController()` and `useChannelCapabilities()`. `useChatContext()` throws outside `Chat`. A component that renders both inside and outside a channel, a channel-list row for example, reads `useChannelInstanceContext()` instead: outside a `Channel` it returns `{}`.

```tsx
import { useChannelInstanceContext } from "stream-chat-react";

export const ChannelName = () => {
  const { channel } = useChannelInstanceContext();
  if (!channel) return null;
  return <span>{channel.cid}</span>;
};
```

### The right owner inside threads

Inside a `Thread`, `useChannel()` returns the thread's channel, so `channel.messagePaginator` and `channel.messageComposer` are the **channel's** messages and composer, not the thread's. Read the composer with `useMessageComposerController()`, and the message list with `useMessagePaginator()`.

### Batched message-list updates

A message paginator's `state` publishes at most once every 500 ms by default, so a burst of events causes about two renders a second instead of one per event. The user's own sends skip the wait and show at once. A component that reads `state.items` can therefore lag up to half a second behind an event. To react to each event as it arrives, listen with `Channel.on()` (see [Events](https://getstream.io/chat/docs/sdk/react/v15/state/state-and-entities/events/)). The interval is a setting of the paginator: see [Message paginators](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/message-paginators/).

## Hooks by state area

The hooks below are exported today. Each table links to its reference page. Where no hook covers a store, read it with `useStateStore`.

### State and entities

Reference: [State and entity hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/state-and-entity-hooks/).

| Hook                        | Reads                                                   |
| --------------------------- | ------------------------------------------------------- |
| `useStateStore`             | any store, through your selector                        |
| `useChatContext`            | `client`, `channelManager`, `searchController`, `mutes` |
| `useChannel`                | the channel of the nearest `Channel` or `Thread`        |
| `useChannelInstanceContext` | the `Channel`'s channel, or `{}`                        |
| `useThreadContext`          | the `Thread`'s thread, or `undefined`                   |
| `useThreadListItemContext`  | the thread of a `ThreadList` row                        |
| `useIsUserMuted`            | whether a user is in `mutes`                            |

### Loading data

Reference: [Loading data hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/loading-data-hooks/).

| Hook                                                                                                   | Reads                                                                  |
| ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| `useChannelListContext`                                                                                | inside `ChannelLists`, the first paginator of `client.channelManager`  |
| `useChannelPaginatorState`                                                                             | a `ChannelPaginator`'s `state`: channels, has more, loading, error     |
| `useMessagePaginator`                                                                                  | the message list of the `Thread`, otherwise of the `Channel`           |
| `useThreadList`                                                                                        | nothing: it reloads `client.threads` and keeps it active while visible |
| `useSearchContext`, `useSearchSourceResultsContext`                                                    | the search controller, and the source whose results are rendered       |
| `useChannelMembersSearch`, `useChannelMediaSearch`, `useChannelFilesSearch`, `usePinnedMessagesSearch` | channel details searches, from `stream-chat-react/channel-detail`      |

For the messages of a message list, use `useStateStore` on the paginator from `useMessagePaginator()`, as `LoadedMessageCount` does. For the thread list, use `useStateStore(client.threads.paginator.state, selector)`.

### Channels and threads

Reference: [Channel and thread hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/channel-and-thread-hooks/).

| Hook                        | Reads                                                              |
| --------------------------- | ------------------------------------------------------------------ |
| `useChannelCapabilities`    | `channel.state` `ownCapabilities`, as a `Set`                      |
| `useChannelMembersState`    | `channel.state` `members`                                          |
| `useChannelMembershipState` | `channel.state` `membership`: the current user's own membership    |
| `useChannelDisplayName`     | the channel's display name                                         |
| `useChannelPreviewInfo`     | the channel's display title and image                              |
| `useCooldownRemaining`      | `channel.cooldownTimer.state`: seconds left in slow mode           |
| `useIsCooldownActive`       | `channel.cooldownTimer.state`: whether the user waits in slow mode |
| `useLastReadData`           | who has read each message                                          |

For anything else in `channel.state`, `thread.state` or `client.threads.state`, use `useStateStore` with your own selector.

### Connection

Reference: [Connection hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/connection-hooks/).

| Hook                                        | Reads                                                             |
| ------------------------------------------- | ----------------------------------------------------------------- |
| `useWSConnectionState`                      | `client.wsConnection.state`: the socket                           |
| `useNetworkConnectionState`                 | `client.networkConnection.state`: the device's network            |
| `useNetworkConnectionStateSelector`         | a selection of `client.networkConnection.state`                   |
| `useReportLostConnectionSystemNotification` | both, to publish the lost-connection notification, as `Chat` does |

### Writing messages

Reference: [Composer hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/composer-hooks/).

| Hook                                                            | Reads                                                                       |
| --------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `useMessageComposerController`                                  | the composer: supplied, the thread's or the channel's                       |
| `useMessageComposerControllerContext`                           | the composer a `MessageComposerControllerProvider` supplies                 |
| `useMessageComposerContext`                                     | the `MessageComposer` component's own context                               |
| `useAttachmentManagerState`                                     | `attachmentManager.state` and the composer's attachment settings            |
| `useAttachmentsForPreview`                                      | the attachments to show as previews                                         |
| `useAttachmentUploadState`, `useAttachmentsUploadState`         | upload progress of one or several attachments                               |
| `useCanCreatePoll`                                              | whether `pollComposer` holds a poll that can be created                     |
| `useMessageComposerHasSendableData`, `useMessageContentIsEmpty` | whether there is something to send                                          |
| `useMessageComposerCommands`                                    | the commands available in the composer                                      |
| `useMessageComposerSubmitFn`, `useMessageComposerBindings`      | the submit function, and the bindings `MessageComposer` puts in its context |

For the composer's other stores (`messageComposer.state`, `textComposer.state`, `pollComposer.state`, `locationComposer.state`, `linkPreviewsManager.state`), use `useStateStore`.

### Notifications, reminders, polls and location

| Hook                                                                                        | Reads                                                     | Reference                                                                          |
| ------------------------------------------------------------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `useNotifications`, `useSystemNotifications`, `useNotificationApi`, `useNotificationTarget` | `client.notifications.store`, and the panel of the caller | [Notification hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/notification-hooks/) |
| `useMessageReminder`                                                                        | `client.reminders.state`, for one message                 | [Reminder hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/reminder-hooks/)         |
| `usePollContext`, `usePollAnswerPagination`, `usePollOptionVotesPagination`                 | the poll in context, and its answers and votes            | [Poll hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/poll-hooks/)                 |
| `useLiveLocationSharingManager`                                                             | a live location sharing manager it creates                | [Location hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/location-hooks/)         |

For a poll's own state, use `useStateStore(poll.state, selector)` with the poll from `usePollContext()`.

Used in: every page that renders state, and the [Reference → Hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/state-and-entity-hooks/) pages.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/ui/reading-state-in-components/](https://getstream.io/chat/docs/sdk/react/v15/ui/reading-state-in-components/).