Indicators
The SDK's loading, error, empty-state and connection 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 and Thread UI.
Best practices
- Replace an indicator through its override key, with
WithComponents. The lists don't take indicators as props. - One component can sit behind several keys. The message list's loading state is the
MessageListLoadingIndicatorkey, whose default isLoadingIndicator, and the thread list has keys of its own, such asThreadListEmptyPlaceholder. Replace the key of the place you want to change. - Switch on
listTypein a replacement ofEmptyStateIndicatororLoadingErrorIndicator. The same key serves the channel list and the message list. - Keep a loading or empty state the size of what it stands in for, so the layout doesn't jump when the data arrives.
- The message lists always render
NetworkDownIndicatorand let it decide whether to show anything. A replacement has to read the connection state and returnnullitself.
Available components
Every component here is exported from stream-chat-react-native and stream-chat-expo.
| Component | What it shows | Where the SDK renders it | Override key |
|---|---|---|---|
LoadingIndicator |
A spinner and a loading text | MessageList and MessageFlashList while their first page loads, and the thread list's next page |
MessageListLoadingIndicator, for the message lists |
LoadingErrorIndicator |
A loading error, with a retry press | ChannelList when its first page fails, Channel when its first page of messages fails, and Thread when its first page of replies fails |
LoadingErrorIndicator |
EmptyStateIndicator |
An icon and a line for an empty list | ChannelList and the message lists of a Channel with no items, and the thread list's default empty state |
EmptyStateIndicator, for the channel list and message lists |
NetworkDownIndicator |
"Waiting for network..." or "Reconnecting..." | The top of MessageList and MessageFlashList |
NetworkDownIndicator |
ChannelListHeaderNetworkDownIndicator |
"Reconnecting..." | The top of ChannelList, while the device or the socket is offline |
ChannelListHeaderNetworkDownIndicator |
LoadingDots |
Three dots that fade in turn | The typing indicators of the message list and the channel list | None |
The typing indicator has a page of its own, TypingIndicator. The channel list's skeleton, its next-page spinner and its error banner are on ChannelListView.
UI customization
LoadingIndicator
The message lists render the MessageListLoadingIndicator key with listType="message" while the paginator loads and there are no messages to show yet. Its default, LoadingIndicator, then shows "Loading messages...". The thread list's default ThreadListLoadingMoreIndicator renders LoadingIndicator with listType="threads", a spinner without text, while the next page of threads loads.
The indicator fills its container and centers a 20-point Spinner above the text. It has the progressbar role, and announces itself through a polite live region on Android.
LoadingErrorIndicator
ChannelList renders the LoadingErrorIndicator key with listType="channel" in place of the list when its query fails before it has channels, with retry reloading the list. Channel renders it with listType="message" in place of its children when the first page of messages fails, and Thread with listType="thread" when the first page of replies fails, each with retry loading it again.
The default shows "Error loading channel list...", "Error loading messages for this channel..." or "Error loading replies...", and the whole indicator is a button that calls retry. With no listType, it shows "Error loading" and isn't pressable. It doesn't show the error's message.
EmptyStateIndicator
ChannelList renders the EmptyStateIndicator key with listType="channel" when it has loaded and has no channels. The message lists render it with listType="message" when a Channel has no messages. Inside a Thread they render nothing, because the parent message shows above the replies.
listType |
The default shows |
|---|---|
'channel' |
An empty bubble and "No conversations yet" |
'message' |
An empty bubble and the indicators.emptyState.noChats.text translation |
'threads' |
An empty bubble and "Reply to a message to start a thread" |
| none | "No items exist" |
The thread list's ThreadListEmptyPlaceholder key defaults to a component that renders EmptyStateIndicator with listType="threads" directly, so an EmptyStateIndicator override doesn't reach the thread list. Override ThreadListEmptyPlaceholder for that.
NetworkDownIndicator and ChannelListHeaderNetworkDownIndicator
NetworkDownIndicator takes no props. It reads useNetworkConnectionState() and useSettledWSConnectionHealth(), and renders:
- "Waiting for network..." while the device reports it's offline;
- "Reconnecting..." once the socket has been down for
client.wsConnection.config.offlineNotificationDisplayDelayMs; - nothing otherwise.
It's positioned absolutely at the top of the message list, over the messages.
ChannelListHeaderNetworkDownIndicator takes no props and always renders "Reconnecting...". ChannelList renders it in the same two cases, and not while its first page loads. Connection status explains the timing.
LoadingDots
LoadingDots renders numberOfDots dots that fade one after the other, and is hidden from screen readers. It has no override key; the typing indicators render it directly.
LoadingProps
| Prop | Type | Default | Description |
|---|---|---|---|
listType |
'channel' | 'message' | 'threads' | 'default' |
Picks the text: "Loading channels...", "Loading messages...", none for 'threads', or "Loading...". |
|
loadingText |
string |
Shown in place of the text listType picks. |
LoadingErrorProps
| Prop | Type | Default | Description |
|---|---|---|---|
listType |
'channel' | 'message' | 'thread' | 'default' |
Picks the text, and whether the indicator is a retry button. | |
retry |
() => void |
a no-op | Called when the user presses the indicator, for 'channel' and 'message'. |
error |
Error |
The error that happened. The SDK passes it; the default doesn't render it. | |
loadNextPage |
() => Promise<void> |
Passed by ChannelList. The default doesn't call it. |
EmptyStateProps
| Prop | Type | Default | Description |
|---|---|---|---|
listType |
'channel' | 'message' | 'threads' | 'default' |
The list it's shown in. See above. |
LoadingDots props
LoadingDots doesn't export a props type.
| Prop | Type | Default | Description |
|---|---|---|---|
diameter |
number |
5 |
The size of each dot, in points. |
duration |
number |
1500 |
The length of one cycle, in milliseconds. |
numberOfDots |
number |
3 |
How many dots. |
spacing |
number |
the theme's loadingDots.spacing |
The space between two dots. |
style |
StyleProp<ViewStyle> |
Applied to the row, after the theme. |
Theming
| Component | Theme keys |
|---|---|
LoadingIndicator |
loadingIndicator.container, loadingIndicator.loadingText, and spinner for its spinner |
LoadingErrorIndicator |
loadingErrorIndicator.container, errorText, retryText |
EmptyStateIndicator |
emptyStateIndicator.channelContainer, channelTitle, messageContainer, messageTitle |
NetworkDownIndicator |
messageList.errorNotification, messageList.errorNotificationText |
ChannelListHeaderNetworkDownIndicator |
channelListHeaderErrorIndicator.container, channelListHeaderErrorIndicator.errorText |
LoadingDots |
loadingDots.container, loadingDots.spacing |
The texts are translation keys of the indicators.* and common.* namespaces; see Localization. Theme object lists the keys.
Example
An empty channel and an empty channel list of your own, from one EmptyStateIndicator:
import { Text, View } from "react-native";
import { EmptyStateIndicator } from "stream-chat-react-native";
import type {
ComponentOverrides,
EmptyStateProps,
} from "stream-chat-react-native";
const AppEmptyState = ({ listType }: EmptyStateProps) => {
if (listType === "message") {
return (
<View style={{ alignItems: "center", flex: 1, justifyContent: "center" }}>
<Text>No messages yet. Say hi!</Text>
</View>
);
}
if (listType === "channel") {
return (
<View style={{ alignItems: "center", padding: 24 }}>
<Text>Join a channel to start.</Text>
</View>
);
}
return <EmptyStateIndicator listType={listType} />;
};
// Declared outside the component, so WithComponents gets the same object on every render.
export const emptyStateOverrides: ComponentOverrides = {
EmptyStateIndicator: AppEmptyState,
};Pass emptyStateOverrides to a WithComponents around ChannelList and Channel. Connection status replaces the two network indicators.
Related hooks
| Hook | Returns |
|---|---|
useNetworkConnectionState() |
whether the device has a network, isOnline |
useSettledWSConnectionHealth() |
whether the socket is healthy, with the display delay applied |
useMessagePaginator() |
the paginator whose isLoading and lastQueryError the message list reads |