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

Loading data hooks

These hooks read channel lists, message lists, the thread list and search results. The paginators behind them are explained under State → Loading data: How pagination works, Channel list paginators, Message paginators and Search sources. How to read state in components is on Reading state in components.

Each entry gives the signature, then:

  • Returns: what the hook gives back.
  • Reads: the store or the context it reads.
  • Re-renders: what makes the component render again.
  • Needs: where the component must sit, and what the hook does outside that place.

useChannelListContext

Returns the channel list paginator of the list around the component.

useChannelListContext(): ChannelListContextValue
  • Returns: { paginator }, or {} when no list provides one.
  • Reads: the context of ChannelLists, which ChannelNavigation renders while no search is active. It provides the first paginator registered on client.channelManager. A ChannelList you render yourself doesn't provide it.
  • Re-renders: when the provided paginator changes. Not when its channels change: read those with useChannelPaginatorState.
  • Needs: nothing. Never throws.

To provide the paginator of a list you render yourself, wrap the list in ChannelListContextProvider. The SDK reads this context to show channel list notifications in the list's panel.

import type { ChannelPaginator } from "stream-chat";
import { ChannelList, ChannelListContextProvider } from "stream-chat-react";

export const ArchivedChannels = ({
  paginator,
}: {
  paginator: ChannelPaginator;
}) => (
  <ChannelListContextProvider value={{ paginator }}>
    <ChannelList paginator={paginator} />
  </ChannelListContextProvider>
);

The context is on ChannelListContext.

useChannelPaginatorState

Returns the loaded channels of a channel list, and its loading state.

useChannelPaginatorState(paginator: ChannelPaginator): ChannelPaginatorView
  • Returns:
    • channels: the loaded channels, in list order. undefined until the first page loads.
    • hasMoreTail: whether more channels can load at the tail, further down the list, with ChannelPaginator.toTail().
    • hasMoreHead: whether more can load at the head, with ChannelPaginator.toHead().
    • isLoading: whether a page is loading.
    • lastQueryError: the error of the last failed query.
  • Reads: paginator.state.
  • Re-renders: when one of these values changes.
  • Needs: no context. Pass a paginator: the hook doesn't accept undefined, so check the one from useChannelListContext() before you render a component that calls it.

channels holds the channels of the loaded pages. A channel added out of order, such as one opened from search, is in it only when it sorts within those pages. Otherwise it appears when the page it belongs to loads. The paginator's fields are on Channel manager and ChannelPaginator.

import type { ChannelPaginator } from "stream-chat";
import { useChannelPaginatorState } from "stream-chat-react";

export const ChannelListFooter = ({
  paginator,
}: {
  paginator: ChannelPaginator;
}) => {
  const { channels, hasMoreTail, isLoading, lastQueryError } =
    useChannelPaginatorState(paginator);

  if (lastQueryError) {
    return (
      <button onClick={() => paginator.reload()} type="button">
        Couldn't load channels. Retry
      </button>
    );
  }
  if (!hasMoreTail) return <span>{channels?.length ?? 0} channels</span>;

  return (
    <button
      disabled={isLoading}
      onClick={() => paginator.toTail()}
      type="button"
    >
      Load more
    </button>
  );
};

useMessagePaginator

Returns the message list of the channel or the thread the component renders in.

useMessagePaginator(): MessagePaginator
  • Returns: thread.messagePaginator inside a Thread, otherwise channel.messagePaginator of the channel from useChannel(). It's the list MessageList and VirtualizedMessageList render.
  • Reads: the thread from useThreadContext() and the channel from useChannel(). No store.
  • Re-renders: when the thread or the channel in context changes. Not when the messages change: read those from paginator.state with useStateStore.
  • Needs: a Channel or a Thread. Throws outside both.
import type { MessagePaginatorState } from "stream-chat";
import { useMessagePaginator, useStateStore } from "stream-chat-react";

const itemsSelector = (state: MessagePaginatorState) => ({
  items: state.items,
});

export const LoadedMessageCount = () => {
  const paginator = useMessagePaginator();
  const { items } = useStateStore(paginator.state, itemsSelector);

  return <span>{items?.length ?? 0} messages loaded</span>;
};

The fields of a message list are on Message paginator, and how it loads on Message paginators.

useThreadList

Keeps client.threads current while a thread list is on screen.

useThreadList(): void

What it does:

  1. When it mounts, it reloads the thread list from the first page.
  2. While the page is visible, it marks the list as in use (client.threads.activate()). When the page becomes visible again, the list reloads if threads got replies in the meantime.
  3. When the page is hidden, and when the component unmounts, it marks the list as unused (client.threads.deactivate()).

Once the list has been in use, it also reloads after a reconnect.

  • Returns: nothing.
  • Reads: no store.
  • Re-renders: never by itself.
  • Needs: Chat. Throws outside it.

ThreadList calls it. Call it yourself in a thread list that doesn't render ThreadList, and read the threads from client.threads.paginator.state:

import type { PaginatorState, Thread, ThreadState } from "stream-chat";
import {
  useChatContext,
  useStateStore,
  useThreadList,
  useWorkspaceNavigation,
} from "stream-chat-react";

const threadsSelector = (state: PaginatorState<Thread>) => ({
  isLoading: state.isLoading,
  threads: state.items,
});

const titleSelector = (state: ThreadState) => ({ title: state.title });

const ThreadRow = ({ thread }: { thread: Thread }) => {
  const { openThread } = useWorkspaceNavigation();
  const { title } = useStateStore(thread.state, titleSelector);

  return (
    <li>
      <button onClick={() => openThread(thread)} type="button">
        {title}
      </button>
    </li>
  );
};

export const CompactThreadList = () => {
  const { client } = useChatContext();
  const { isLoading, threads } = useStateStore(
    client.threads.paginator.state,
    threadsSelector,
  );
  useThreadList();

  if (isLoading && !threads?.length) return <span>Loading threads…</span>;

  return (
    <ul>
      {threads?.map((thread) => (
        <ThreadRow key={thread.id} thread={thread} />
      ))}
    </ul>
  );
};

The thread list is explained on Replies and threads, and its API on Thread and thread manager.

useSearchContext

Returns the search controller and the settings of the Search around the component.

useSearchContext(): SearchContextValue
  • Returns:
    • searchController: the controller from useChatContext().
    • containerRef and filterButtonsContainerRef: the search container and its filter buttons.
    • directMessagingChannelType, disabled, exitSearchOnInputBlur, inputProps and placeholder: the Search props of the same names.
  • Reads: the context Search provides. No store.
  • Re-renders: when Search provides a new value. It does so each time it renders, for example when a search starts or ends. Not when the query or the results change: read searchController.state (isActive, searchQuery, sources) with useStateStore.
  • Needs: Search. Throws outside it.

useSearchSourceResultsContext

Returns the search source whose results are being rendered.

useSearchSourceResultsContext(): SearchSourceResultsContextValue
  • Returns: { searchSource }.
  • Reads: the context SearchSourceResults sets. Once the user types a query, SearchResults renders one SearchSourceResults for each active source. No store.
  • Re-renders: when SearchSourceResults provides a new value. It does so each time it renders, which follows the source's items and isLoading. For any field you show, read searchSource.state (items, isLoading, hasNext, searchQuery, lastQueryError) with useStateStore.
  • Needs: SearchSourceResults. Throws outside it.

A results header that counts the results:

import type { SearchSourceState } from "stream-chat";
import {
  useSearchSourceResultsContext,
  useStateStore,
} from "stream-chat-react";

const resultsSelector = (state: SearchSourceState) => ({
  isLoading: state.isLoading,
  items: state.items,
});

export const ResultCountHeader = () => {
  const { searchSource } = useSearchSourceResultsContext();
  const { isLoading, items } = useStateStore(
    searchSource.state,
    resultsSelector,
  );

  return (
    <div>
      {searchSource.type}: {isLoading ? "searching…" : (items?.length ?? 0)}
    </div>
  );
};

Pass it to WithComponents as SearchSourceResultsHeader. The search components are on Search components.

Channel details search hooks

stream-chat-react/channel-detail exports one hook for each list in the channel details panel. Each builds its own search source for the channel and loads the first page when it mounts. The members and pinned messages hooks wait until the channel has members or pinned messages.

useChannelMembersSearch(): { displayedMembers: ChannelMemberResponse[]; handleSearchChange: (query: string) => void; hasMembers: boolean; membersSearchSource: ChannelMemberSearchSource; resetMembersSearch: () => void; searchInputResetKey: number }
useChannelMediaSearch(): { channelMediaSearchSource: MessageSearchSource; hasNext: boolean; hasResultsLoaded: boolean; isLoading: boolean; mediaItems: ChannelMediaItem[] }
useChannelFilesSearch(): { channelFilesSearchSource: MessageSearchSource; fileItems: ChannelFileItem[]; groupCounts: number[]; hasResultsLoaded: boolean; isLoading: boolean; sections: ChannelFileSection[] }
usePinnedMessagesSearch(params?: UsePinnedMessagesSearchParams): { displayedMessages: Array<MessageResponse | LocalMessage>; handleSearchChange: (query: string) => void; hasPinnedMessages: boolean; hasSearchResultsLoaded: boolean; pinnedMessagesSearchSource: MessageSearchSource }
Hook Searches
useChannelMembersSearch the channel's members. Until the first results arrive, displayedMembers holds the members the channel has loaded.
useChannelMediaSearch the channel's messages with image or video attachments
useChannelFilesSearch the channel's messages with file or audio attachments, grouped by month
usePinnedMessagesSearch the channel's pinned messages. Pass { searchSource } to use your own source with its own filters.
  • Reads: the state of the source each hook builds.
  • Re-renders: when that source's results or loading state change.
  • Needs: ChannelDetail, or a ChannelDetailProvider. Throws outside both. The media, files and pinned messages hooks also need Chat.

What each one returns is on Channel Detail Views.

Lists without a hook

Read these lists with useStateStore and your own selector:

List Store
The thread list client.threads.paginator.state
Search searchController.state, and state on each source