Overview

The client keeps everything you render in reactive state stores (updated from API calls, WebSocket events, etc.). This chapter shows where each kind of data lives and how to read it.

The model

  1. State is a store you can subscribe to. Read the current value with getLatestValue(). Subscribe to changes with subscribe()/subscribeWithSelector().

  2. Every list is a paginator. Messages, replies, pinned messages, channel and thread lists, and reminders share one API. See Pagination.

  3. Each channel and thread is a singleton. Get its instance with client.channel() or client.threads.ensure().

  4. Timestamps are nanosecond numbers. Convert them with nsToDate(message.created_at).

Reading state

The same calls work on every store in this chapter.

// Read the current value
const { items } = channel.messagePaginator.state.getLatestValue();

// React to any change in the store
const unsubscribeChannel = channel.state.subscribe((state) =>
  renderChannel(state),
);

// React to changes of selected values only.
// Return a small, flat object: it is compared key by key.
const unsubscribeHeader = channel.state.subscribeWithSelector(
  ({ typing, memberCount }) => ({ typing, memberCount }),
  ({ typing, memberCount }) => renderHeader(typing, memberCount),
);

// Stop listening
unsubscribeChannel();
unsubscribeHeader();

In React, use useStateStore:

const { items } = useStateStore(channel.messagePaginator.state, (state) => ({
  items: state.items,
}));

Pagination

Lists have paginator state that works this way:

A paginator's state holds the loaded items, isLoading, hasMoreHead, hasMoreTail and lastQueryError. items is undefined until the first page loads.

const paginator = channel.messagePaginator;

const { items, isLoading, hasMoreTail, lastQueryError } =
  paginator.state.getLatestValue();

// Load the next page
if (paginator.hasMoreTail) {
  await paginator.toTail();
}

// Load the first page again
await paginator.reload();

toTail() loads the next page toward the end of the list: older messages in a message list, and the next channels, threads or reminders in the others. toHead() loads toward the start. You only need it in a message list after jumping to a message, to load the newer messages.

A failed page doesn't throw. The error is in lastQueryError, and calling toTail() again retries.

To change what a list loads, such as its filters, sort or page size, see the "Configure the query" section of its page.

v9 vs v10

See how the most important concepts compare in v9 and v10:

v9 v10
channel.state.messages channel.messagePaginator.state → items
channel.state.pinnedMessages channel.pinnedMessagesPaginator.state → items
thread.state.replies thread.messagePaginator.state → items
channelManager.state.channels channelPaginator.state → items
threadManager.state.threads client.threads.paginator.state → items
client.configs[cid] channel.serverConfig
connection.changed event client.wsConnection.state → isHealthy
created_at as a Date nanosecond number, convert with nsToDate()