# Indicators

> **Info:** This is release candidate documentation for **Stream Chat React SDK v15**. For the latest stable version, see the [latest version (v14)](https://getstream.io/chat/docs/sdk/react/).

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](https://getstream.io/chat/docs/sdk/react/v15/ui/components/channel-list-ui/#loading-empty-and-error-states), [Message list UI](https://getstream.io/chat/docs/sdk/react/v15/ui/components/message-list-ui/), [Thread UI](https://getstream.io/chat/docs/sdk/react/v15/ui/components/thread-ui/#loading-empty-and-error-states) and [Channel UI](https://getstream.io/chat/docs/sdk/react/v15/ui/components/channel-ui/#typing-indicator).

## 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](https://getstream.io/chat/docs/sdk/react/v15/ui/components/channel-list-ui/#loading-empty-and-error-states).

### LoadingIndicator

These parts render the `LoadingIndicator` override: `VirtualizedMessageList` while it loads a page of messages, `MessageList` while it loads an older page (its [`loadingMore`](https://getstream.io/chat/docs/sdk/react/v15/component-reference/message-list/messagelist/#props) 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](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/overriding-components/#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](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/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

```tsx
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

```tsx
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

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

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

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/component-reference/utilities/indicators/](https://getstream.io/chat/docs/sdk/react/v15/component-reference/utilities/indicators/).