Channel manager and ChannelPaginator
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. Creating and registering channel paginators is explained on 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.
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:
hasResultsistrueonceitemsis an array, even an empty one.isInitializedistrueonce a query has run or the SDK has filled in a page, untilpaginator.resetState().
This handler runs whenever a list's items, loading state or error changes:
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, rather than through paginator.ingestItem(). 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 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:
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. 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 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 mentionhidden.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, andChannelPaginator.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 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 lists the built-in handlers, and 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. |
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 explains what counts as using a channel.
See also
- How pagination works explains loading, staying live and ordering.
- Channel list paginators explains channel lists and the channel manager.
- Message paginator lists the API of message lists.
- Channel lists shows channel lists in the UI.