# Reactive state

> **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/).

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.

Reading stores in React components is on [Reading state in components](https://getstream.io/chat/docs/sdk/react/v15/ui/reading-state-in-components/).

## 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: each key of the new result, using `Object.is`. It calls the handler only if one of them 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 the previous emission's 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/v15/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.

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/v15/state/state-and-entities/events/) that change them.

---

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