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

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