# VirtualizedMessageList

`VirtualizedMessageList` renders a scrollable list of messages with built-in virtualization. Virtualization renders only what’s visible to keep the DOM small and scrolling smooth, which is ideal for high-traffic channels (for example, livestreams).

Like `MessageList`, it renders each message based on `message.type`, including date separators, reactions, system/deleted messages, and standard text/attachment messages.

By default, it loads the most recent 25 messages from `channel.state` and fetches older messages on scroll-up. Loaded messages live in `ChannelStateContext`:

```tsx
const { messages } = useChannelStateContext();
```

`VirtualizedMessageList` has no required props and reads data from the `Channel` contexts provided by [`Channel`](/chat/docs/sdk/react/v13/components/core-components/channel/). Customize message rendering via the [Message UI component](/chat/docs/sdk/react/v13/components/message-components/message_ui/).

## Best Practices

- Prefer virtualization for long-lived or high-traffic channels.
- Set `defaultItemHeight` if you see scroll jumps from tall messages.
- Keep custom renderers lightweight to preserve virtualization gains.
- Avoid passing `messages` unless you need to control the list source.
- Test grouping and separators with virtualization enabled.

## Basic Usage

`VirtualizedMessageList` must be rendered under `Channel`. You can pass a `messages` prop to override the default `ChannelStateContext` messages.

**Example 1** - without `messages` prop

```tsx
<Chat client={client}>
  <ChannelList />
  <Channel>
    <VirtualizedMessageList />
    <MessageInput />
  </Channel>
</Chat>
```

**Example 2** - with `messages` prop

```tsx
const customMessages = [
  // array of messages
];

<Chat client={client}>
  <ChannelList />
  <Channel>
    <VirtualizedMessageList messages={customMessages} />
    <MessageInput />
  </Channel>
</Chat>;
```

## Scroll Behavior Optimization

By default, the virtualized list uses the latest message (first rendered) as the default item size.
This can lead to inaccurate scroll thumb size or scroll jumps if the last item is unusually tall (for example, an attachment).
To improve this behavior, set the `defaultItemHeight` property to a value close (or equal to) the height of the usual messages.

```tsx
<VirtualizedMessageList messages={customMessages} defaultItemHeight={76} />
```

## Message grouping

`VirtualizedMessageList` groups messages by position (`'middle' | 'top' | 'bottom' | 'single'`) and adds a class to each `<li>` (for example, `str-chat__li str-chat__li--bottom`) to support grouped styling.

## Props

### additionalMessageInputProps

Additional props to be passed to the `MessageInput` component, [available props](/chat/docs/sdk/react/v13/components/message-input-components/message_input/#props/). It is rendered when editing a message.

| Type   |
| ------ |
| object |

### additionalVirtuosoProps

Additional props to be passed the underlying [`react-virtuoso` virtualized list dependency](https://virtuoso.dev/virtuoso-api-reference/).

| Type   |
| ------ |
| object |

### closeReactionSelectorOnClick

If true, picking a reaction from the `ReactionSelector` component will close the selector.

| Type    | Default |
| ------- | ------- |
| boolean | false   |

### customMessageRenderer

Custom message render function, overrides the default `messageRenderer` function defined in the component.

| Type                                                                 |
| -------------------------------------------------------------------- |
| ( messages: RenderedMessage[], index: number ) => React.ReactElement |

### defaultItemHeight

If set, the default item height is used for the calculation of the total list height. Use if you expect messages with a lot of height variance.

| Type   |
| ------ |
| number |

### disableDateSeparator

If true, disables the injection of date separator UI components.

| Type    | Default |
| ------- | ------- |
| boolean | true    |

### groupStyles

Callback function to set group styles for each message.

| Type                                                                                                                                                                     |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| (message: RenderedMessage, previousMessage: RenderedMessage, nextMessage: RenderedMessage, noGroupByUser: boolean, maxTimeBetweenGroupedMessages?: number) => GroupStyle |

### hasMore

Whether the list has more items to load.

| Type    | Default                                                                                                           |
| ------- | ----------------------------------------------------------------------------------------------------------------- |
| boolean | [ChannelStateContextValue['hasMore']](/chat/docs/sdk/react/v13/components/contexts/channel_state_context#hasmore) |

### hideDeletedMessages

If true, removes the `MessageDeleted` components from the list.

| Type    | Default |
| ------- | ------- |
| boolean | false   |

### hideNewMessageSeparator

If true, hides the `DateSeparator` component that renders when new messages are received in a channel that's watched but not active.

| Type    | Default |
| ------- | ------- |
| boolean | false   |

### loadingMore

Whether the list is currently loading more items.

| Type    | Default                                                                                                                   |
| ------- | ------------------------------------------------------------------------------------------------------------------------- |
| boolean | [ChannelStateContextValue['loadingMore']](/chat/docs/sdk/react/v13/components/contexts/channel_state_context#loadingmore) |

### loadMore

Function called when more messages are to be loaded, provide your own function to override the handler stored in context.

| Type     | Default                                                                                                               |
| -------- | --------------------------------------------------------------------------------------------------------------------- |
| function | [ChannelActionContextValue['loadMore']](/chat/docs/sdk/react/v13/components/contexts/channel_action_context#loadmore) |

### maxTimeBetweenGroupedMessages

Maximum time in milliseconds that should occur between messages to still consider them grouped together.

| Type   |
| ------ |
| number |

### Message

Custom UI component to display an individual message.

| Type      | Default                                                                                                              |
| --------- | -------------------------------------------------------------------------------------------------------------------- |
| component | [MessageSimple](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageSimple.tsx) |

### messageActions

Array of allowed message actions (ex: 'edit', 'delete', 'reply'). To disable all actions, provide an empty array.

| Type  | Default                                                                                                        |
| ----- | -------------------------------------------------------------------------------------------------------------- |
| array | ['edit', 'delete', 'flag', 'markUnread', 'mute', 'pin', 'quote', 'react', 'remindMe', 'reply', 'saveForLater'] |

It is also possible to display actions that are by default supported by the SDK, but not active by default:

```tsx
import {
  VirtualizedMessageList,
  MESSAGE_ACTIONS,
  OPTIONAL_MESSAGE_ACTIONS,
} from "stream-chat-react";

const messageActions = [
  ...Object.keys(MESSAGE_ACTIONS),
  OPTIONAL_MESSAGE_ACTIONS.deleteForMe, // has to be explicitly added to the messageActions array
];

<VirtualizedMessageList messageActions={messageActions} />;
```

### messageLimit

The limit to use when paginating messages (the page size).

<admonition type="warning">

After mounting, the `VirtualizedMessageList` component checks if the list is completely filled with messages. If there is some space left in the list, `VirtualizedMessageList` will load the next page of messages, but it will do so _only once_. This means that if your `messageLimit` is too low, or if your viewport is very large, the list will not be completely filled. Set the limit with this in mind.

</admonition>

| Type   | Default |
| ------ | ------- |
| number | 100     |

### messages

The messages to render in the list, provide your own array to override the data stored in context.

| Type  | Default                                                                                                             |
| ----- | ------------------------------------------------------------------------------------------------------------------- |
| array | [ChannelStateContextValue['messages']](/chat/docs/sdk/react/v13/components/contexts/channel_state_context#messages) |

### openThread

Custom handler invoked when the button in the `Message` component that opens [`Thread`](/chat/docs/sdk/react/v13/components/core-components/thread/) component is clicked. To be able to define custom logic to `openThread`, we need to have a wrapper around `VirtualizedMessageList` component and reach out to `ChannelActionContext` for the default `openThread` function.

```tsx
import { useCallback } from "react";
import {
  VirtualizedMessageList,
  useChannelActionContext,
} from "stream-chat-react";
import type { LocalMessage } from "stream-chat";

const MessageListWrapper = () => {
  const { openThread: contextOpenThread } = useChannelActionContext();

  const openThread = useCallback(
    (message: LocalMessage, event?: React.BaseSyntheticEvent) => {
      // custom logic
      contextOpenThread(message, event);
    },
    [contextOpenThread],
  );

  return <VirtualizedMessageList openThread={openThread} />;
};
```

| Type                                                                | Default                                                                                                                   |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `(message: LocalMessage, event?: React.BaseSyntheticEvent) => void` | [ChannelActionContextValue['openThread']](/chat/docs/sdk/react/v13/components/contexts/channel_action_context#openthread) |

### overscan

The amount of extra content the list should render in addition to what's necessary to fill in the viewport.

| Type   | Default |
| ------ | ------- |
| number | 0       |

### renderText

Custom function to render message text content.

| Type     | Default                                                                                                                   |
| -------- | ------------------------------------------------------------------------------------------------------------------------- |
| function | [renderText](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/renderText/renderText.tsx) |

### returnAllReadData

Keep track of read receipts for each message sent by the user. When disabled, only the last own message delivery / read status is rendered.

| Type    | Default |
| ------- | ------- |
| boolean | false   |

### reviewProcessedMessage

Allows to review changes introduced to messages array on per message basis (for example date separator injection before a message). The array returned from the function is appended to the array of messages that are later rendered into React elements in the `VirtualizedMessageList`.

The function expects a single parameter, which is an object containing the following attributes:

- `changes` - array of messages representing the changes applied around a given processed message
- `context` - configuration params and information forwarded from `processMessages`
- `index` - index of the processed message in the original messages array
- `messages` - array of messages retrieved from the back-end
- `processedMessages` - newly built array of messages to be later rendered

The `context` has the following parameters:

- `userId` - the connected user ID;
- `enableDateSeparator` - flag determining whether the date separators will be injected Enable date separator
- `hideDeletedMessages` - flag determining whether deleted messages would be filtered out during the processing
- `hideNewMessageSeparator` - disables date separator display for unread incoming messages
- `lastRead`: Date when the channel has been last read. Sets the threshold after everything is considered unread

The example below demonstrates how the custom logic can decide, whether deleted messages should be rendered on a given date. In this example, the deleted messages neither the date separator would be rendered if all the messages on a given date are deleted.

```js
const getMsgDate = (msg) =>
  (msg &&
    msg.created_at &&
    isDate(msg.created_at) &&
    msg.created_at.toDateString()) ||
  "";

const dateSeparatorFilter = (msg) => msg.customType !== "message.date";

const msgIsDeleted = (msg) => msg.type === "deleted";

const reviewProcessedMessage = ({
  changes,
  index,
  messages,
  processedMessages,
}) => {
  const changesWithoutSeparator = changes.filter(dateSeparatorFilter);
  const dateSeparatorInjected =
    changesWithoutSeparator.length !== changes.length;
  const previousProcessedMessage =
    processedMessages[processedMessages.length - 1];
  const processedMessage = messages[index];
  const processedMessageDate = getMsgDate(processedMessage);

  if (dateSeparatorInjected) {
    if (!processedMessageDate) return changes;
    const followingMessages = messages.slice(index + 1);
    let allFollowingMessagesOnDateDeleted = false;

    for (const followingMsg of followingMessages) {
      const followingMsgDate = getMsgDate(followingMsg);
      if (followingMsgDate !== processedMessageDate) break;
      allFollowingMessagesOnDateDeleted = followingMsg.type === "deleted";
    }

    return allFollowingMessagesOnDateDeleted ? [] : changes;
  } else if (
    msgIsDeleted(processedMessage) &&
    getMsgDate(previousProcessedMessage) !== getMsgDate(processedMessage)
  ) {
    return [];
  } else {
    return changes;
  }
};
```

### scrollSeekPlaceHolder

Custom data passed to the list that determines when message placeholders should be shown during fast scrolling.

| Type   |
| ------ |
| object |

### scrollToLatestMessageOnFocus

If true, the list will scroll to the latest message when the window regains focus.

| Type    | Default |
| ------- | ------- |
| boolean | false   |

### shouldGroupByUser

If true, group messages belonging to the same user, otherwise show each message individually.

| Type    | Default |
| ------- | ------- |
| boolean | false   |

### separateGiphyPreview

If true, the Giphy preview will render as a separate component above the `MessageInput`, rather than inline with the other messages in the list.

| Type    | Default |
| ------- | ------- |
| boolean | false   |

### showUnreadNotificationAlways

The floating notification informing about unread messages will be shown when the `UnreadMessagesSeparator` is not visible. The default is false, that means the notification
is shown only when viewing unread messages.

| Type    | Default |
| ------- | ------- |
| boolean | false   |

### stickToBottomScrollBehavior

The scroll-to behavior when new messages appear. Use `'smooth'` for regular chat channels and `'auto'`
(which results in instant scroll to bottom) if you expect high throughput.

| Type               | Default  |
| ------------------ | -------- |
| 'smooth' \| 'auto' | 'smooth' |

### reactionDetailsSort

Sort options to provide to a reactions query. Affects the order of reacted users in the default reactions modal.

| Type                     | Default                     |
| ------------------------ | --------------------------- |
| `{ created_at: number }` | reverse chronological order |

### sortReactions

Comparator function to sort reactions. Should have the same signature as an array's `sort` method.

| Type                                                     | Default             |
| -------------------------------------------------------- | ------------------- |
| (this: ReactionSummary, that: ReactionSummary) => number | chronological order |

### threadList

If true, indicates that the current `VirtualizedMessageList` component is part of a `Thread`.

| Type    | Default |
| ------- | ------- |
| boolean | false   |


---

This page was last updated at 2026-04-21T07:55:47.203Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v13/components/core-components/virtualized_list/](https://getstream.io/chat/docs/sdk/react/v13/components/core-components/virtualized_list/).