# Channel manager and ChannelPaginator

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

Every list in `stream-chat` is a paginator: a channel list, a channel's or a thread's messages, the pinned messages, the thread list and the reminders. This page lists what all of them share, the API of `ChannelPaginator`, and the API of `client.channelManager`, which keeps channel lists current and stores the client's channels.

How paginators load, stay live and order their items is explained on [How pagination works](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/how-pagination-works/). Creating and registering channel paginators is explained on [Channel list paginators](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/channel-list-paginators/).

## The base paginator

Every list extends `BasePaginator`. What follows applies to all of them. Message lists add their own API on top, listed on [Message paginator](https://getstream.io/chat/docs/sdk/react/v15/reference/state/message-paginator/).

### State

`paginator.state` is a store with these fields:

| Field            | Type                             | What it holds                                                            |
| ---------------- | -------------------------------- | ------------------------------------------------------------------------ |
| `items`          | `T[]` or `undefined`             | The loaded items, in list order. `undefined` until a page loads.         |
| `isLoading`      | `boolean`                        | A page is loading.                                                       |
| `hasMoreTail`    | `boolean`                        | There are more items toward the tail, the end of the sort order.         |
| `hasMoreHead`    | `boolean`                        | There are more items toward the head, the end the first page loads from. |
| `lastQueryError` | `Error` or `undefined`           | The error of the last failed load. A successful load clears it.          |
| `cursor`         | `PaginatorCursor` or `undefined` | With cursor pagination, the cursor of each direction.                    |
| `offset`         | `number` or `undefined`          | With offset pagination, how many items the loaded pages hold.            |

The getters `items`, `isLoading`, `hasMoreTail`, `hasMoreHead`, `lastQueryError`, `cursor` and `offset` read the same values once. Two more getters derive from them:

- `hasResults` is `true` once `items` is an array, even an empty one.
- `isInitialized` is `true` once a query has run or the SDK has filled in a page, until `paginator.resetState()`.

This handler runs whenever a list's items, loading state or error changes:

```ts
const unsubscribe = paginator.state.subscribeWithSelector(
  ({ isLoading, items, lastQueryError }) => ({
    isLoading,
    items,
    lastQueryError,
  }),
  ({ isLoading, items, lastQueryError }) => {
    if (lastQueryError) console.error(lastQueryError);
    console.log(`${items?.length ?? 0} loaded`, { isLoading });
  },
);
```

### Loading

| Method                    | Returns         | What it does                                                                                                                                                                       |
| ------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `toTail(params)`          | `Promise`       | Loads the next page toward the tail. Does nothing while a page loads, or when `hasMoreTail` is `false`.                                                                            |
| `toHead(params)`          | `Promise`       | Loads the next page toward the head, with the same rules.                                                                                                                          |
| `toTailDebounced(params)` | `void`          | `toTail()` once calls stop for `debounceMs`.                                                                                                                                       |
| `toHeadDebounced(params)` | `void`          | `toHead()` once calls stop for `debounceMs`.                                                                                                                                       |
| `cancelScheduledQuery()`  | `void`          | Cancels a debounced load that hasn't started.                                                                                                                                      |
| `reload()`                | `Promise<void>` | Clears the list and loads its first page. `items` is `undefined` and `isLoading` is `true` until it arrives. The thread list's `ThreadPaginator.reload()` keeps its items instead. |
| `resetState()`            | `void`          | Clears the list without loading. The next load is a first page.                                                                                                                    |

`params` is optional and takes these fields:

| Field               | What it does                                                                                                             |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `reset`             | `"yes"` starts again from the first page. `"no"` continues. `"auto"`, the default, starts again when the query changed.  |
| `keepPreviousItems` | With `reset: "yes"`, keeps the loaded items on screen and merges the first page into them, instead of clearing the list. |
| `retryCount`        | How many times to retry a failed request, for this call only.                                                            |
| `silent`            | Doesn't set `isLoading` for this load, unless it's a first page that clears the list.                                    |
| `updateState`       | `false` loads the page without showing it.                                                                               |
| `requestOptions`    | Options for the request that don't change which page it asks for. Each list reads its own.                               |

`paginator.toTail({ keepPreviousItems: true, reset: "yes" })` refreshes a paginator's first page in place. After a reconnect, the client refreshes channel lists with `ChannelPaginator.recoverLoadedItems()` instead, which queries every loaded page again.

### Items and ordering

| Method                          | Returns              | What it does                                                                                                               |
| ------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `getItem(id)`                   | `T` or `undefined`   | A loaded item by id.                                                                                                       |
| `matchesFilter(item)`           | `boolean`            | Whether the item matches the list's filters on the client.                                                                 |
| `boost(id, { ttlMs, until })`   | `void`               | Puts the item at the head until the boost expires: `ttlMs` from now, at `until`, or after 15 seconds.                      |
| `getBoost(id)`, `isBoosted(id)` | the boost, `boolean` | The item's boost, and whether it's still in effect.                                                                        |
| `removeBoost(id)`               | `void`               | Ends the boost.                                                                                                            |
| `ingestItem(item)`              | `boolean`            | Places a changed item at its sort position, or removes it when it no longer matches. The client calls it as events arrive. |
| `removeItem({ id, item })`      | the item's position  | Removes an item from the list.                                                                                             |

A boost changes only the order on screen and shows the next time the paginator publishes its items. App code changes items through the SDK's methods, such as [message operations](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-operations/), rather than through `paginator.ingestItem()`. [Ordering and boosting](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/how-pagination-works/#ordering-and-boosting) explains both.

### Configuration

A paginator's options go in `paginatorOptions` when it's created. For the message lists the SDK creates, they go in `client.config`, as [Message paginator](https://getstream.io/chat/docs/sdk/react/v15/reference/state/message-paginator/#loading-more) shows. `client.config` takes all of them except `doRequest`. The options:

| Option            | Default | What it does                                                                                                                      |
| ----------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `pageSize`        | `10`    | How many items a page asks for. Each kind of list sets its own default.                                                           |
| `debounceMs`      | `300`   | How long the debounced loads wait.                                                                                                |
| `retryCount`      | `0`     | How many times a failed request is retried, one second apart.                                                                     |
| `throwErrors`     | `false` | With `true`, a failed load, whether the request fails or placing its page does, rejects instead of only setting `lastQueryError`. |
| `lockItemOrder`   | `false` | With `true`, an updated item keeps its position on screen.                                                                        |
| `maxLoadedItems`  | none    | Caps how many items the newest interval keeps. Used by the message paginators.                                                    |
| `stateThrottleMs` | none    | Publishes live changes to `items` at most once per interval. Channel and thread message lists use 500 ms.                         |
| `doRequest`       | none    | Replaces the request a page sends. Resolves with `{ items }`, and a `cursor` with cursor pagination.                              |

`paginator.config` holds the values in effect, and `paginator.configState` holds them as a store. `paginator.updateConfig(patch)` changes them, and `paginator.pageSize` reads and sets the page size.

## ChannelPaginator

A `ChannelPaginator` is a channel list. It's the one list an app creates:

```ts
import { ChannelPaginator } from "stream-chat";

const inbox = new ChannelPaginator({
  client,
  filters: { archived: false, members: { $in: [userId] }, type: "messaging" },
  id: "inbox",
  paginatorOptions: { pageSize: 30 },
  requestOptions: { message_limit: 25, presence: true },
  sort: [{ direction: -1, field: "last_message_at" }],
});

client.channelManager.insertPaginator({ paginator: inbox });
```

### Options

| Option                  | What it holds                                                                                                                         |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `client`                | The client. Required.                                                                                                                 |
| `id`                    | The list's id, used by ownership rules and `client.channelManager.getPaginatorById()`. A random id without one.                       |
| `filters`               | `ChannelFilters`, sent with every page and matched on the client as events arrive.                                                    |
| `sort`                  | `SortParamRequest[]`. By default `last_message_at`, then `updated_at`, both newest first.                                             |
| `requestOptions`        | The other `queryChannels` options, such as `message_limit`, `member_limit`, `presence`, `state` and `watch`. Not `limit` or `offset`. |
| `paginatorOptions`      | The [base options](#configuration). A channel list retries 3 times by default.                                                        |
| `filterBuilderOptions`  | Filters generated from a context. See `filterBuilder` below.                                                                          |
| `channelStateOptions`   | How each page's channels are applied to the client's state.                                                                           |
| `sortComparatorFactory` | A function that returns the comparator ordering the list. It receives `{ sort, defaultComparator }`.                                  |

Each page is a `client.queryChannelsAndHydrate()` request, which watches the channels it returns unless `requestOptions.watch` is `false`. With `paginatorOptions.doRequest`, that function sends the request instead.

### Properties

| Property                            | What it holds                                                                                                               |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `id`                                | The list's id.                                                                                                              |
| `staticFilters`                     | The `filters` passed to the constructor. Settable.                                                                          |
| `sort`                              | The sort. Settable.                                                                                                         |
| `options`                           | The `requestOptions`. Settable.                                                                                             |
| `channelStateOptions`               | Settable.                                                                                                                   |
| `sortComparatorFactory`             | Settable. `undefined` goes back to the built-in ordering.                                                                   |
| `filterBuilder`                     | A `FilterBuilder`. `FilterBuilder.updateContext(context)` changes the generated filters.                                    |
| `predefinedFilter`                  | For a `predefined_filter` query, what the server reported it filtered and sorted by.                                        |
| `effectiveFilters`, `effectiveSort` | The filters and sort the list matches and orders by: the server's predefined filter when there is one, else the local ones. |

Setting `staticFilters` or `sort`, or changing the `filterBuilder` context, doesn't reload the list. Call `ChannelPaginator.reload()` afterwards, as [Changing filters or sort](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/channel-list-paginators/#changing-filters-or-sort) explains.

### Methods

A channel list has the base paginator's methods. These differ:

- `ChannelPaginator.ingestItem(channel)` refuses a provisional channel, and lists the client's stored instance when it's given another instance of the same cid.
- `ChannelPaginator.matchesFilter(channel)` never matches a hidden channel unless the filters mention `hidden`.
- `ChannelPaginator.recoverLoadedItems()` queries again, page by page, the channels the paginator has loaded, up to 150, keeping them on screen. Then it removes the loaded channels no page returned, and drops those beyond the first 150. Connection recovery calls it for every registered paginator that has loaded.
- `ChannelPaginator.buildQueryFilters()` returns the filters the next page sends, and `ChannelPaginator.buildMatchFilters()` the filters channels are matched against on the client.

## Multi-list channel orchestration

`client.channelManager` is a `ChannelManager` that the client creates. It does two jobs: it keeps the registered channel lists current from one set of event listeners, and it stores the client's channels, one per cid.

### Registering lists

| Method                                  | Returns                           | What it does                                                                                                                                 |
| --------------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `insertPaginator({ paginator, index })` | `void`                            | Adds a list at `index`, or at the end. A list with the same id moves to the new position.                                                    |
| `setPaginators(paginators)`             | `void`                            | Replaces the whole set in one update. A repeated id keeps its first list.                                                                    |
| `removePaginator(paginatorOrId)`        | `ChannelPaginator` or `undefined` | Stops event updates for a list. Its loaded channels stay, so it can be registered again.                                                     |
| `clearPaginators()`                     | `ChannelPaginator[]`              | Removes every list, and returns them.                                                                                                        |
| `getPaginatorById(id)`                  | `ChannelPaginator` or `undefined` | A registered list.                                                                                                                           |
| `reload()`                              | `Promise`                         | Reloads every registered list.                                                                                                               |
| `recover()`                             | `Promise`                         | Calls `ChannelPaginator.recoverLoadedItems()` on every registered paginator that has loaded. Connection recovery calls it after a reconnect. |
| `resetPaginatorStates()`                | `void`                            | Clears every list's loaded channels and keeps the lists registered.                                                                          |

`client.channelManager.state` holds the registered lists, in order, as `paginators`, and the `paginators` getter reads them once.

### Ownership

| Method                                          | What it does                                                                                                                                                   |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `setOwnershipResolver(resolver)`                | Sets which lists keep a channel that matches several. Pass list ids, highest priority first, or a function. Without an argument, every matching list keeps it. |
| `resolveOwnership(channel, matchingPaginators)` | Returns the ids of the lists that keep the channel. An empty set means all of them.                                                                            |
| `ingestChannel(channel)`                        | Places a channel in the lists that match and own it, and removes it from the others. Ignores a provisional channel.                                            |

A resolver function receives `{ channel, matchingPaginators }` and returns an array of list ids. `createPriorityOwnershipResolver(ids)` builds the one an array of ids stands for. [Channels that match several paginators](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/channel-list-paginators/#channels-that-match-several-paginators) explains the rules.

### Event handling

| Method                                                                            | Returns      | What it does                                                                              |
| --------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------- |
| `registerSubscriptions()`                                                         | `() => void` | Starts handling events, and returns the function that stops it. Calls are counted.        |
| `addEventHandler({ eventType, handle, id, index, replace, revertOnUnsubscribe })` | `() => void` | Inserts a handler in the event type's pipeline, and returns the function that removes it. |
| `setEventHandlers({ eventType, handlers })`                                       | `void`       | Replaces every handler of the event type.                                                 |
| `removeEventHandlers({ eventType, handlers })`                                    | `void`       | Removes handlers given as `{ handler }`, or by id as `{ idMatch: { id } }`.               |
| `ChannelManager.getDefaultHandlers()`                                             | the handlers | A copy of the built-in handlers, by event type.                                           |

`ignoreEventsForUnknownChannels` is a handler that stops an event for a channel the client doesn't store. The React SDK registers the subscriptions for you; code that uses `stream-chat` alone calls `client.channelManager.registerSubscriptions()`. [How events move channels between paginators](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/channel-list-paginators/#how-events-move-channels-between-paginators) lists the built-in handlers, and [Customizing event handling](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/channel-list-paginators/#customizing-event-handling) shows how to add one.

### Stored channels

| Method                       | Returns                  | What it does                                                                                                                                      |
| ---------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ensure({ type, id, data })` | `Channel`                | The stored channel, or a new one. See [Getting a channel](https://getstream.io/chat/docs/sdk/react/v15/reference/state/channel-and-channel-state/#getting-a-channel). |
| `get(cid)`                   | `Channel` or `undefined` | The stored channel for a cid. It can be one that was disposed.                                                                                    |
| `values()`                   | `Channel[]`              | Every stored channel.                                                                                                                             |
| `releaseUnusedChannels()`    | `void`                   | Removes disposed channels, then the channels nothing uses, and disposes them. The SDK never calls it.                                             |

[Entity lifecycle](https://getstream.io/chat/docs/sdk/react/v15/state/state-and-entities/entity-lifecycle/#cleaning-up-unused-channels) explains what counts as using a channel.

## See also

- [How pagination works](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/how-pagination-works/) explains loading, staying live and ordering.
- [Channel list paginators](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/channel-list-paginators/) explains channel lists and the channel manager.
- [Message paginator](https://getstream.io/chat/docs/sdk/react/v15/reference/state/message-paginator/) lists the API of message lists.
- [Channel lists](https://getstream.io/chat/docs/sdk/react/v15/features/channels/channel-lists/) shows channel lists in the UI.

---

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