# Overview

The client keeps everything you render in reactive state stores (updated from API calls, WebSocket events, etc.). This chapter shows where each kind of data lives and how to read it.

## The model

1. **State is a store you can subscribe to.** Read the current value with `getLatestValue()`. Subscribe to changes with `subscribe()`/`subscribeWithSelector()`.

2. **Every list is a paginator.** Messages, replies, pinned messages, channel and thread lists, and reminders share one API. See [Pagination](#pagination).

3. **Each channel and thread is a singleton.** Get its instance with `client.channel()` or `client.threads.ensure()`.

4. **Timestamps are nanosecond numbers.** Convert them with `nsToDate(message.created_at)`.

## Reading state

The same calls work on every store in this chapter.

```js label="JavaScript"
// Read the current value
const { items } = channel.messagePaginator.state.getLatestValue();

// React to any change in the store
const unsubscribeChannel = channel.state.subscribe((state) =>
  renderChannel(state),
);

// React to changes of selected values only.
// Return a small, flat object: it is compared key by key.
const unsubscribeHeader = channel.state.subscribeWithSelector(
  ({ typing, memberCount }) => ({ typing, memberCount }),
  ({ typing, memberCount }) => renderHeader(typing, memberCount),
);

// Stop listening
unsubscribeChannel();
unsubscribeHeader();
```

In React, use `useStateStore`:

```tsx label="React"
const { items } = useStateStore(channel.messagePaginator.state, (state) => ({
  items: state.items,
}));
```

## Pagination

Lists have paginator state that works this way:

A paginator's `state` holds the loaded `items`, `isLoading`, `hasMoreHead`, `hasMoreTail` and `lastQueryError`. `items` is `undefined` until the first page loads.

```js label="JavaScript"
const paginator = channel.messagePaginator;

const { items, isLoading, hasMoreTail, lastQueryError } =
  paginator.state.getLatestValue();

// Load the next page
if (paginator.hasMoreTail) {
  await paginator.toTail();
}

// Load the first page again
await paginator.reload();
```

`toTail()` loads the next page toward the end of the list: older messages in a message list, and the next channels, threads or reminders in the others. `toHead()` loads toward the start. You only need it in a message list after [jumping to a message](https://getstream.io/chat/docs/javascript/state-messages/#jumping-to-a-message), to load the newer messages.

A failed page doesn't throw. The error is in `lastQueryError`, and calling `toTail()` again retries.

To change what a list loads, such as its filters, sort or page size, see the "Configure the query" section of its page.

## v9 vs v10

See how the most important concepts compare in v9 and v10:

| v9                              | v10                                               |
| ------------------------------- | ------------------------------------------------- |
| `channel.state.messages`        | `channel.messagePaginator.state` → `items`        |
| `channel.state.pinnedMessages`  | `channel.pinnedMessagesPaginator.state` → `items` |
| `thread.state.replies`          | `thread.messagePaginator.state` → `items`         |
| `channelManager.state.channels` | `channelPaginator.state` → `items`                |
| `threadManager.state.threads`   | `client.threads.paginator.state` → `items`        |
| `client.configs[cid]`           | `channel.serverConfig`                            |
| `connection.changed` event      | `client.wsConnection.state` → `isHealthy`         |
| `created_at` as a `Date`        | nanosecond number, convert with `nsToDate()`      |

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/javascript/state-overview/](https://getstream.io/chat/docs/javascript/state-overview/).