List states UI

The SDK's lists show a state of their own while they load, when a load fails, and when they're empty. Use this page to know which states each list has, which ones you can replace, and what happens when a first page fails to load.

States and their keys

Each cell names the override key that replaces the state. "Built in" means the list renders a fixed component that no key replaces. "None" means the list has no such state. To register a replacement, see How overrides merge. ComponentsContext lists each key's default.

List First page loading First page failed Next page loading Empty
ChannelList ChannelListLoadingIndicator, with Skeleton for each row LoadingErrorIndicator ChannelListFooterLoadingIndicator EmptyStateIndicator
MessageList, MessageFlashList MessageListLoadingIndicator Channel renders LoadingErrorIndicator instead The list's FooterComponent or HeaderComponent prop, for older messages EmptyStateIndicator, not in a thread
Thread None Thread renders LoadingErrorIndicator instead None None
ThreadList ThreadListLoadingIndicator None ThreadListLoadingMoreIndicator ThreadListEmptyPlaceholder
ChannelMemberList, ChannelAddMembersForm Built in: placeholder rows A notification Built in: a spinner Built in: "No members found", or "No user found"
PinnedMessageList, MediaList, FileAttachmentList Built in: placeholders A notification Built in: a spinner Built in: a message with an icon
MessageUserReactions, the list of who reacted None: the list shows once the first page loaded A notification None None
Poll answers and a poll option's votes None None None None
The suggestion list in the composer None None None None

Two states of the channel list sit outside the table: the connection banner and the error banner over the top of the list. They're in The channel list.

ThreadList has no first-page error state, and Thread has no first-page loading state of its own: it reloads a stale thread while its message list shows the parent message. When a thread holds no replies and its first page loads, the message list renders MessageListLoadingIndicator. The default components get these props:

  • LoadingErrorIndicator gets listType ('channel', 'message' or 'thread', which picks the text), error and retry.
  • EmptyStateIndicator gets listType ('channel', 'message' or 'threads').
  • MessageListLoadingIndicator renders LoadingIndicator with listType='message', and the next-page indicator of the thread list renders it with listType='threads'.

The channel list

State What the list renders Override key
Loading the first page Placeholder rows, numberOfSkeletons of them (8 by default) ChannelListLoadingIndicator, Skeleton
Loading the next page A spinner after the last row ChannelListFooterLoadingIndicator
Refreshing The pull-to-refresh spinner above the rows None
No channels "No conversations yet". The component gets listType="channel". EmptyStateIndicator
The first page failed "Error loading channel list...". Tapping it loads the first page again. LoadingErrorIndicator
A query failed while channels are on screen A banner over the top of the list. Tapping it refreshes the list. ChannelListHeaderErrorIndicator
No network, or the connection is down A "Reconnecting..." banner over the top of the list ChannelListHeaderNetworkDownIndicator

When the first page fails

  1. The query fails, after the paginator's retries: a channel list paginator retries a failed query three times.
  2. The paginator records the error as lastQueryError in paginator.state, and the list renders LoadingErrorIndicator in place of its rows.
  3. Tapping it calls ChannelPaginator.reload() on the list's paginator, which loads the first page again.

With offline support on, a failure while the list has channels isn't recorded, so the channels from the local database stay on screen without an error. When a query fails while channels are on screen, for example the next page or a pull to refresh, the list keeps the rows and shows the banner over the top, and tapping it refreshes the list. A pull within 5 seconds of the last refresh is ignored, unless the last query failed. Neither the list nor the paginator publishes a notification for a failed query.

After a reconnect, client.connectionRecovery re-queries the channels the list has loaded by itself. The list doesn't show a separate state for it.

The message lists and the thread

State What the list renders Override key
Loading the first page A spinner with "Loading messages..." in place of the list MessageListLoadingIndicator
Loading older messages A spinner at the older end. In a thread, under the parent message. None (the list's prop)
Loading newer messages A spinner at the newer end None (the list's prop)
A channel with no messages EmptyStateIndicator with listType="message". A thread with no replies shows its parent message instead. EmptyStateIndicator
The first page failed Channel renders LoadingErrorIndicator in place of its children, "Error loading messages for this channel...". Tapping it loads the latest messages again. LoadingErrorIndicator

A failed load of older or newer messages is recorded as lastQueryError in the paginator's state, and the list stays as it is. The next scroll to that end tries again.

Channel renders the error state when the message list holds no messages and its query failed. It replaces everything inside Channel, including a header you render as a child. In a thread, Thread renders LoadingErrorIndicator with listType="thread", "Error loading replies...", when the first page of replies fails, and tapping it loads the replies again.

The thread list

State What the list renders Override key
Loading the first page Ten placeholder rows ThreadListLoadingIndicator
Loading the next page A spinner after the last row ThreadListLoadingMoreIndicator
No threads "Reply to a message to start a thread" ThreadListEmptyPlaceholder

A reload keeps the loaded threads on screen until the new ones arrive. When a query fails, the list keeps what it had loaded, or shows the empty placeholder if it had nothing. The error is recorded as lastQueryError in client.threads.paginator.state. Only the unread banner shows a failure, when the reload it started fails: see The unread banner.

Channel details lists

The lists in channel details, which ChannelDetails and its screens render, load their items through search sources: the members, the users to add as members, the pinned messages, the photos and videos, and the files. Each list creates its source when it opens, and loads the next page as the user scrolls.

List While it loads When it's empty When a query fails
ChannelMemberList Placeholder rows "No members found", for a search without a match The notification api:channel:query-members:failed
ChannelAddMembersForm Placeholder rows "No user found" The notification api:channel:query-users:failed
PinnedMessageList Placeholder rows "No pinned messages", with a hint to long-press a message; "No pinned messages" for a search without a match The notification api:channel:query-pinned-messages:failed
MediaList Placeholder tiles "No photos or videos" The notification api:channel:query-media:failed
FileAttachmentList Placeholder rows "No files" The notification api:channel:query-file-attachments:failed

While a next page loads, each list shows a spinner after its last item. A failed query shows an error notification, "Failed to load pinned messages" for example, in the notification list of the screen, and leaves the list as it was. The lists have no retry control: scrolling to the end of the list loads the next page again. See Notifications UI for the notifications the SDK publishes.

Other lists

  • Who reacted. MessageUserReactions loads the reactions of a message, 25 at a time, and renders its list once the first page has loaded. A failed load publishes the notification api:message:reactions:fetch:failed, "Error fetching reactions", and the list stays empty.
  • A poll's answers and a poll option's votes. The lists load the next page as the user scrolls and render no loading, empty or error state of their own. usePollAnswersPagination() and usePollOptionVotesPagination() return loading and error for a list of your own.
  • The suggestion list in the composer. It loads more suggestions as the user scrolls, and shows only while it has suggestions, so it has none of these states.

Replacing a state in one list only

EmptyStateIndicator and LoadingErrorIndicator are read by more than one list, so a replacement registered around the whole app replaces them everywhere. The channel list renders EmptyStateIndicator with listType="channel" and the message list with listType="message", and LoadingErrorIndicator gets listType 'channel', 'message' or 'thread'. A replacement checks listType and renders the default for the lists it leaves alone, as the example in Replacing parts on Channel list UI does. To replace a state for one list only, register it in a WithComponents around that list only:

Where the list renders Wrap
The channel list The ChannelList
The message list and the composer The Channel, or the Thread for a thread
The thread list The ThreadList