Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

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:

  • 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:

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 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 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