Pinned messages paginator
A channel's pinned messages are a list of their own: channel.pinnedMessagesPaginator, a PinnedMessagePaginator. Every channel creates one with itself, so apps never construct it. This page lists its API in stream-chat.
It's a list like the others, with the API listed in The base paginator. The pinned messages as a feature, with their UI, are on Pinned messages.
State
channel.pinnedMessagesPaginator.state has the base paginator's fields:
| Field | What it holds |
|---|---|
items |
The loaded pinned messages, oldest pin first. undefined until the first page loads. |
isLoading |
A page is loading. |
hasMoreTail |
There are earlier pins to load. |
hasMoreHead |
There are later pins to load, after a jump. |
lastQueryError |
The error of the last failed load. A successful load clears it. |
The list holds only messages with pinned: true that aren't shadowed, ordered by pinned_at. Its head is the most recent pin, at the end of items. To show the most recent pin first, reverse items.
Each pinned message is the same object the channel's message list holds. An edit, a reaction or a soft delete shows in both lists.
This handler logs the pins whenever they change, most recent first:
const { pinnedMessagesPaginator } = channel;
const unsubscribe = pinnedMessagesPaginator.state.subscribeWithSelector(
({ hasMoreTail, items }) => ({ hasMoreTail, items }),
({ hasMoreTail, items }) => {
const latestFirst = [...(items ?? [])].reverse();
console.log(
latestFirst.map((message) => message.text),
{ hasMoreTail },
);
},
);Unlike a channel's message list, the pinned list has no unread state, no live view and no aggregateState. It has no update throttle by default either: every change publishes at once.
Loading more
The first page comes with the channel. When the channel is queried or watched, the response carries a page of its pinned messages. The first response's pins become the paginator's first page, so no separate request is needed. Pins from later responses are merged into the loaded pins without removing any, and don't move the paginator off a pin it jumped to.
That first page is measured against the paginator's page size. When the response carries fewer pins than a page, hasMoreTail is false, even if the channel has more pins than the response carried. Call PinnedMessagePaginator.reload() to load a full first page.
| Method | What it does |
|---|---|
toTail(params) |
Loads earlier pins, through Channel.getPinnedMessages(). |
toHead(params) |
Loads later pins, after a jump. |
toTailDebounced(params) |
toTail() once calls stop, for a scroll handler. |
reload() |
Clears the list and loads its first page from Channel.getPinnedMessages(). |
jumpToMessage(messageId, options) |
Shows the interval that holds the pinned message, loading a page around it if needed. |
jumpToTheLatestMessage(options) |
Shows the most recent pins. |
The jumps take the options listed in Jumping to a message, and publish a focus signal in pinnedMessagesPaginator.messageFocusSignal the same way. A pinned message jumped to here is in the pinned paginator's interval. To show it in the conversation, jump the channel's message paginator too: MessagePaginator.jumpToMessage(id).
A page holds 100 pins by default. Set the page size under channel.pinnedMessagesPaginator in client.config. The shared messagePaginator key doesn't apply to this list:
client.config.set({
channel: { pinnedMessagesPaginator: { pageSize: 25 } },
});The other base options go under the same key.
For a one-off look at the pinned messages, without a list that stays current, call Channel.getPinnedMessages(). It returns { messages } and changes no state.
What updates it
Pinning is a change to the message. client.pinMessage(message) and client.unpinMessage(message) change it on the server and update nothing locally. The list changes when the server's message.updated event arrives:
| Event | What changes |
|---|---|
message.updated with pinned: true |
The message joins the list at its pinned_at position. |
message.updated with pinned: false |
The message leaves the list. |
message.new with pinned: true |
The message joins the list. |
message.updated, message.undeleted, message.deleted |
The message changes in place. A hard delete removes it. |
user.messages.deleted, and user.deleted when it deletes the user's messages |
That user's pinned messages are marked deleted, or removed on a hard delete. |
user.updated |
That user's pinned messages get the new user object. |
channel.truncated |
Pinned messages sent before the cutoff are removed. Without a cutoff, the list is emptied. |
channel.hidden with clear_history |
The list is emptied. |
A message deleted through MessageOperations.delete() changes at once, before the server answers. A hard delete removes it from the list.
message.updated skips replies. A reply that is pinned, with a parent_id, joins the list when the list loads it, not from the event.
This pins a message, and logs the pins once the event has added it:
const { pinnedMessagesPaginator } = channel;
const unsubscribe = pinnedMessagesPaginator.state.subscribeWithSelector(
({ items }) => ({ items }),
({ items }) => {
console.log(`${items?.length ?? 0} pinned messages`);
},
);
// Nothing changes locally until message.updated arrives.
await client.pinMessage(message);See also
- Pinned messages shows pinning in the UI.
- How pagination works explains loading and staying live.
- Message paginator lists the API of the channel's message list.
- Channel and channel state lists the API of the channel.