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

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 later pages load toward.
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.
dispose() void Releases the list's hold on its items and cancels pending publishes. Call it on a list you create once you're done with it, and don't use the list afterwards.

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 stores the page without showing it, unless the page lands in the interval on screen.
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, as ChannelList does on pull to refresh. 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, seq }) void Puts the item first in items until the boost expires: ttlMs from now, at until, or after 15 seconds. Among boosted items, the higher seq comes first.
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. A value set through paginator.updateConfig() or pageSize replaces the resolved one until the paginator's configuration in client.config changes again.

ChannelPaginator

A ChannelPaginator is a channel list. It's the one list an app creates. ChannelList renders the paginator you pass in its paginator prop:

import { ChannelPaginator } from "stream-chat";
import type { StreamChat } from "stream-chat";

export const createInbox = (client: StreamChat, userId: string) => {
  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 });
  return inbox;
};

ChannelList doesn't register the paginator with client.channelManager, so insert it yourself, as above, or the list doesn't change as events arrive. In a component, create the paginator once, for example with useMemo, and remove it with client.channelManager.removePaginator() when you're done with it.

ChannelList drives the paginator:

  • While items is undefined, it calls paginator.reload(), on mount and again after client.disconnectUser() resets the list.
  • When the user scrolls near the end, it calls paginator.toTail().
  • On pull to refresh, it calls paginator.toTail({ keepPreviousItems: true, reset: "yes" }), at most once every 5 seconds unless the last load failed.

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.

With offline support

With offline support, a channel list works with the local database:

  • The first load of a list shows the channels the database holds for the same query at once, with offlineMode: true. The request to the server waits until the client has sent its pending tasks and synced the database after connecting. Its first page then replaces those channels.
  • Each time the list's channels or their order change, the list writes the order of its first page to the database under its query, so the next launch opens on it.

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. Each mounted ChannelList calls client.channelManager.registerSubscriptions() and releases it when it unmounts, so the manager handles events while at least one list is on screen. Code that keeps lists current without a mounted ChannelList calls client.channelManager.registerSubscriptions() itself. 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
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.

To get a channel, or create one the client then stores, call client.channel(), as Getting a channel explains. Entity lifecycle explains what counts as using a channel.

See also