Skip to content
+

Chat - Core selectors

Read from the chat store with memoized selectors that drive granular, efficient subscriptions.

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

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

The demo below shows selectors driving row-level subscriptions in a chat thread:

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
isLoadingHistory boolean Whether a history fetch is in flight
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>;
}

Calling 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