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

Indicators

The SDK's loading, empty-state, pagination and typing indicators. This page lists where each one renders, how to replace it, and its props. The Components pages show them in context: Channel list UI, Message list UI, Thread UI and Channel UI.

Best practices

  • Replace an indicator through its override key, with WithComponents. The lists don't take indicators as props.
  • The channel list has keys of its own, such as EmptyListIndicator. An EmptyStateIndicator override doesn't reach it.
  • Keep a loading or empty state the size of what it stands in for, so the layout doesn't jump when the data arrives.
  • Treat TypingIndicator, at the bottom of the message list, and TypingIndicatorHeader, in the header, as two separate parts.
  • To change every spinner at once, replace the IconLoading icon: LoadingIndicator renders it.

Available components

Every component here imports from stream-chat-react.

Component What it shows Where the SDK renders it Override key
EmptyStateIndicator A message for an empty list MessageList and VirtualizedMessageList with no messages. The channel list's default empty state. EmptyStateIndicator, for the message lists
LoadingChannels Five placeholder rows The channel list and the thread list, while they load their first page FirstPageLoadingIndicator, for the channel list
LoadingChannel A placeholder header, three messages and a composer Nowhere. Render it yourself while a channel loads. None
LoadingErrorIndicator "Error:" and the error's message Nowhere None
LoadingIndicator A spinner The message lists while they load a page, the thread list's next page, upload previews, and other spinners LoadingIndicator, for the parts listed below
LoadMoreButton A "Load more" button, with a spinner while loading Only inside LoadMorePaginator None
LoadMorePaginator Its children and a LoadMoreButton Nowhere. The channel list loads its next page on scroll. None
TypingIndicator The avatars of the people typing and animated dots The bottom of MessageList, and of VirtualizedMessageList when the key is set TypingIndicator
TypingIndicatorHeader "Cleo is typing" and animated dots The subtitle of ChannelHeader and ThreadHeader None

UI customization

EmptyStateIndicator

The message lists render the EmptyStateIndicator override with listType="message" in a channel and listType="thread" in a thread. The default shows "Send a message to start the conversation", or messageText when passed, and nothing in a thread.

The other lists have their own keys:

  • the channel list renders EmptyListIndicator, which receives no props and defaults to <EmptyStateIndicator listType="channel" />, "No conversations yet";
  • the thread list renders ThreadListEmptyPlaceholder.

LoadingErrorIndicator

LoadingErrorIndicator renders "Error: " and error.message, and nothing without an error. The SDK doesn't render it, and it has no override key. Render it yourself where a load of your own can fail. A failed channel list query is reported as a notification instead: see Channel list UI.

LoadingIndicator

These parts render the LoadingIndicator override: VirtualizedMessageList while it loads a page of messages, MessageList while it loads an older page (its loadingMore prop changes when), the thread list's default ThreadListLoadingIndicator, an attachment preview while the server confirms its upload, and a video attachment while its player loads. The SDK passes it no props that change how it looks.

Other spinners render LoadingIndicator directly, not through the key: the channel list's next page, LoadMoreButton, the unseen threads banner, poll answer and vote lists, the image gallery, and the lists in channel details. All of them, through the key or not, render the IconLoading icon, so replacing that icon changes every spinner. Icons shows how.

LoadMorePaginator and LoadMoreButton

LoadMorePaginator renders its children and, while hasNextPage is true, a LoadMoreButton that calls loadNextPage. Use them for a list of your own. ChannelList has no paginator prop: it loads the next page when it's scrolled to the bottom, and shows LoadingNextPageIndicator meanwhile.

TypingIndicator

MessageList passes the TypingIndicator override isMessageListScrolledToBottom and scrollToBottom. It shows only while the list is scrolled to the latest message, and scrolls the list down when it appears. VirtualizedMessageList has no default: it renders a typing indicator only when the TypingIndicator key is set.

It reads the typing events from the composer that useMessageComposerController() returns, leaves out the current user, and renders nothing when typing events are off for the channel. Typing events reach the channel's composer only, so inside a Thread it shows nothing. Typing indicators covers the feature end to end.

TypingIndicatorHeader

TypingIndicatorHeader replaces the subtitle of ChannelHeader and ThreadHeader while someone is typing. It has no override key: a custom header renders it itself. Without threadList, it shows typing in the channel. With threadList, it looks for typing in the thread of the Thread around it, but like TypingIndicator it reads the thread's composer there, which typing events don't reach, so it shows nothing in ThreadHeader.

EmptyStateIndicatorProps

Prop Type Default Description
listType "channel" | "message" | "thread" The list it's shown in. With no listType, the default shows "No items exist".
messageText string For listType="message", the text to show instead of the default. It's passed through translation.

LoadingErrorIndicatorProps

Prop Type Default Description
error Error | null The error to show. Without one, the indicator renders nothing.

LoadingIndicatorProps

LoadingIndicatorProps are the props of an SVG element, plus:

Prop Type Default Description
decorative boolean true Hides the spinner from assistive technology and keeps it out of the tab order.

It passes them to the IconLoading icon, with the class str-chat__loading-indicator in place of any className you pass.

LoadMoreButtonProps

Prop Type Default Description
onClick React.MouseEventHandler<HTMLButtonElement> required Loads the next page.
isLoading boolean Disables the button and shows a spinner instead of its label.
children ReactNode "Load more" The label.

LoadMorePaginatorProps

Prop Type Default Description
loadNextPage () => void required Called by the button.
hasNextPage boolean Shows the button while true.
isLoading boolean Passed to the button.
LoadMoreButton React.ComponentType<LoadMoreButtonProps> LoadMoreButton The button to render.
reverse boolean Puts the button before the children instead of after them.
children ReactNode The list.

LoadMorePaginatorProps also accepts hasPreviousPage, loadPreviousPage, LoadingIndicator and threshold, from the shared PaginatorProps type, but LoadMorePaginator doesn't read them.

TypingIndicatorProps

Prop Type Default Description
isMessageListScrolledToBottom boolean true When false, the indicator renders nothing.
scrollToBottom () => void Called when the indicator appears while the list is scrolled to the bottom.

Whether it shows typing in the channel or in a thread depends on whether it renders inside a Thread, not on a prop.

TypingIndicatorHeaderProps

Prop Type Default Description
threadList boolean false Looks for typing in the current thread instead of in the channel. Inside a Thread, it finds none: see above.

Examples

Replace the message list's empty state

import { Channel, MessageList, WithComponents } from "stream-chat-react";
import type { EmptyStateIndicatorProps } from "stream-chat-react";

const EmptyChannel = ({ listType }: EmptyStateIndicatorProps) =>
  listType === "message" ? (
    <p className="app-empty-channel">No messages yet. Say hi!</p>
  ) : null;

export const App = () => (
  <WithComponents overrides={{ EmptyStateIndicator: EmptyChannel }}>
    <Channel channel={channel}>
      <MessageList />
    </Channel>
  </WithComponents>
);

Returning null for "thread" keeps a thread's list empty, as the default does.

Replace the channel list's empty state

import { ChannelList, WithComponents } from "stream-chat-react";

const NoChannels = () => (
  <p className="app-no-channels">Join a channel to start.</p>
);

export const Channels = () => (
  <WithComponents overrides={{ EmptyListIndicator: NoChannels }}>
    <ChannelList paginator={paginator} />
  </WithComponents>
);

Show typing in VirtualizedMessageList

import {
  Channel,
  TypingIndicator,
  VirtualizedMessageList,
  WithComponents,
} from "stream-chat-react";

export const App = () => (
  <WithComponents overrides={{ TypingIndicator }}>
    <Channel channel={channel}>
      <VirtualizedMessageList />
    </Channel>
  </WithComponents>
);