Skip to content
+

Chat - Core selectors

Subscribe to exactly the store slices you need with memoized selectors for efficient, granular rendering.

chatSelectors is a collection of memoized selectors that read from the normalized chat store. They power the built-in hooks and can also be used directly with useChatStore() for advanced subscriptions.

import { chatSelectors, useChatStore } from '@mui/x-chat/headless';

The following demo uses selectors for efficient rendering:

Selector-driven thread

Update one controlled message from the parent to see only the matching row rerender.

Selector-driven thread
selector-1 · renders 0

MUI Agent

Row 1 is subscribed independently.

selector-2 · renders 0

Alice

Row 2 is subscribed independently.

selector-3 · renders 0

MUI Agent

Row 3 is subscribed independently.

selector-4 · renders 0

Alice

Row 4 is subscribed independently.

selector-5 · renders 0

MUI Agent

Row 5 is subscribed independently.

selector-6 · renders 0

Alice

Row 6 is subscribed independently.

selector-7 · renders 0

MUI Agent

Row 7 is subscribed independently.

selector-8 · renders 0

Alice

Row 8 is subscribed independently.

selector-9 · renders 0

MUI Agent

Row 9 is subscribed independently.

selector-10 · renders 0

Alice

Row 10 is subscribed independently.

selector-11 · renders 0

MUI Agent

Row 11 is subscribed independently.

selector-12 · renders 0

Alice

Row 12 is subscribed independently.

selector-13 · renders 0

MUI Agent

Row 13 is subscribed independently.

selector-14 · renders 0

Alice

Row 14 is subscribed independently.

Selector reference

Direct state selectors

These selectors read a single field from the store and return it directly:

Selector Return type Description
messageIds string[] Ordered message IDs
messagesById Record<string, ChatMessage> Message map by ID
conversationIds string[] Ordered conversation IDs
conversationsById Record<string, ChatConversation> Conversation map by ID
activeConversationId string | undefined Active conversation ID
isStreaming boolean Whether a stream is active
hasMoreHistory boolean Whether more history can be loaded
error ChatError | null Current runtime error
composerValue string Current draft text
composerAttachments ChatDraftAttachment[] Draft attachments

Derived selectors

These selectors combine multiple store fields and memoize the result:

Selector Return type Description
messages ChatMessage[] All messages as an array (derived from IDs + map)
conversations ChatConversation[] All conversations as an array
activeConversation ChatConversation | undefined The active conversation record
messageCount number Number of messages
conversationCount number Number of conversations

Parameterized selectors

Selector Signature Description
message (state, id: string) => ChatMessage | undefined Single message by ID
conversation (state, id: string) => ChatConversation | undefined Single conversation by ID
typingUserIds (state, conversationId?: string) => string[] User IDs typing in a conversation (defaults to active)

Using selectors with useChatStore()

The hooks useMessageIds(), useMessage(id), and others are convenience wrappers around useChatStore() + chatSelectors. When you need a custom derived value, use the store directly:

import { useChatStore, chatSelectors } from '@mui/x-chat/headless';
import { useStore } from '@mui/x-internals/store';

function MessageCounter() {
  const store = useChatStore();
  const count = useStore(store, chatSelectors.messageCount);

  return <span>{count} messages</span>;
}

Parameterized selectors

For selectors that take an argument, pass a selector function:

function ConversationTitle({ id }: { id: string }) {
  const store = useChatStore();
  const conversation = useStore(store, chatSelectors.conversation, id);

  return <span>{conversation?.title ?? 'Untitled'}</span>;
}

Why normalization matters

The store keeps messages and conversations in a normalized shape (ids + byId maps) rather than flat arrays. This design has three benefits:

  1. Point updates — Updating a single message during streaming does not rebuild the message array. Only the messagesById record changes.
  2. Stable references — The messageIds array only changes when messages are added or removed, not when their content updates. useMessageIds() stays stable during streaming.
  3. Memoized derivation — The messages selector rebuilds the array only when either messageIds or messagesById changes, and the result is reference-equal when inputs are unchanged.

This is why the useMessageIds() + useMessage(id) pattern performs well for large threads — the ID list stays stable while individual rows subscribe to their own message record.

See also

API