# Reactive state

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

The state layer keeps its data in stores. A store holds one value, a plain object, and tells its subscribers when that value changes. Every object in `stream-chat` whose data changes over time keeps it in a store: a channel, a thread, a list, a composer, the connection.

For example, `channel.messagePaginator.state` holds the channel's loaded messages. When a new message arrives, the client replaces the store's value with one that includes the message, and everything subscribed to the store hears about it.

Components read stores with `useStateStore`, which is on [Reading state in components](https://getstream.io/chat/docs/sdk/react-native/v10/ui/reading-state-in-components/). This page covers the stores themselves, which work the same outside React.

## Stores

A store can be read in two ways:

- `StateStore.getLatestValue()` returns the current value. It subscribes to nothing.
- `StateStore.subscribe(handler)` calls `handler` with the current value right away, and again after every change. It returns a function that ends the subscription.

Most code needs part of the value, not all of it. `StateStore.subscribeWithSelector(selector, handler)` calls `handler` right away, and after that only when the selected part changes:

```ts
const unsubscribe = channel.messagePaginator.state.subscribeWithSelector(
  ({ isLoading, items }) => ({ isLoading, items }),
  ({ isLoading, items }) => {
    console.log(isLoading ? "Loading..." : `${items?.length ?? 0} messages`);
  },
);

// When the messages are no longer shown:
unsubscribe();
```

Each change replaces the value with a new object. A part that changed is a new object or array, and a part that didn't keeps its reference. Each time the store emits a new value, it runs the selector on it and compares the result with the selector's result for the previous emission, key by key, using `Object.is`. It calls the handler only if one of the keys differs. That gives three rules for a selector:

- **Return an object with fixed keys.** Write `({ members }) => ({ members })`, not `(state) => state.members`. When the selector returns the slice itself, its keys are member ids, and a removed member goes unnoticed because no remaining key changed.
- **Select values; don't build them.** A selector that filters or maps returns a new array on every change, which never equals the previous one, so the handler runs every time. Select the array and derive what you need in the handler.
- **Keep the same keys every time.** Between two emissions, only the keys of the new result are compared, so a key that the previous result had and the new one leaves out isn't checked at all. For example, `({ quotedMessage }) => (quotedMessage ? { quotedMessage } : {})` returns `{ quotedMessage }` while a quote is set, and `{}` in the emission that removes it. The new result has no key to compare, the handler doesn't run, and the quote stays on screen. Write `({ quotedMessage }) => ({ quotedMessage })` instead. The key is always there, and its change to `undefined` is seen.

Stores are `StateStore` objects from the `@stream-io/state-store` package, which `stream-chat` depends on. Import `StateStore` from there when your code names the type or creates stores of its own.

### The SDK writes, you read

A store also has `StateStore.next()` and `StateStore.partialNext()`, which replace its value. The SDK writes its own stores as events arrive and requests complete. Change state through the objects' methods instead, for example `channel.messageComposer.setQuotedMessage(message)` rather than a write to the composer's store. A value you write yourself skips everything the method does, and the SDK may overwrite it with its next update.

### Where the stores are

| Object        | Store                                                                                                               | What it holds                                                                                                        |
| ------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Channel       | `channel.state`                                                                                                     | members, read state, typing users, watchers, capabilities, `data`, mute status, loading flags                        |
| Message list  | `channel.messagePaginator.state`, `thread.messagePaginator.state`                                                   | the loaded messages, `isLoading`, `hasMoreHead`, `hasMoreTail`                                                       |
| Composer      | `messageComposer.state`, and `state` on each part: `textComposer`, `attachmentManager`, `pollComposer` and the rest | the message being written                                                                                            |
| Thread        | `thread.state`                                                                                                      | the parent message, participants, read state, reply count                                                            |
| Thread list   | `client.threads.state`, `client.threads.paginator.state`                                                            | `unreadThreadCount` and `unseenThreadIds`; the loaded threads                                                        |
| Channel list  | `paginator.state` on each `ChannelPaginator`                                                                        | the loaded channels, `isLoading`, `hasMoreTail`                                                                      |
| Connection    | `client.wsConnection.state`, `client.networkConnection.state`                                                       | WebSocket health, device network                                                                                     |
| Notifications | `client.notifications.store`                                                                                        | the published notifications                                                                                          |
| Settings      | `configState` on each configurable object                                                                           | the settings in effect, see [Configuration](https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/configuration/) |

## Getters aren't reactive

Many objects have getters that read a store for you: `composer.quotedMessage`, `channel.data`, `channel.state.unreadCount`, `thread.ownUnreadCount`. A getter returns the value at the moment you read it, and doesn't tell you when it changes. Code that reads a getter once keeps showing the old value after the next change. The same goes for a component that reads a getter while it renders: nothing re-renders it when the value changes.

To follow a value, subscribe to the store the getter reads:

```ts
// Read once. Nothing tells this code when the quoted message changes.
const quoted = channel.messageComposer.quotedMessage;

// Runs now, and again whenever the user quotes another message or removes the quote.
const unsubscribe = channel.messageComposer.state.subscribeWithSelector(
  ({ quotedMessage }) => ({ quotedMessage }),
  ({ quotedMessage }) => {
    console.log(quotedMessage ? `Replying to ${quotedMessage.id}` : "No quote");
  },
);
```

A few values have no store yet, among them the connected user (`client.user`) and `client.mutedChannels`. Read them when you need them, or listen for the [events](https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/events/) that change them.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/reactive-state/](https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/reactive-state/).