Skip to content
+

Chat - Advanced store access

Use the store escape hatch to subscribe to exactly the runtime slices you want.

Bypass the convenience hooks and work directly with the normalized store to build custom dashboards, metrics, or specialized derived views:

  • useChatStore() to get the underlying store instance
  • chatSelectors to read specific store slices
  • custom selector subscriptions for derived values

Key concepts

Getting the store

useChatStore() returns the ChatStore<Cursor> instance created by ChatProvider:

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

function Dashboard() {
  const store = useChatStore();
  const messageCount = useStore(store, chatSelectors.messageCount);
  const conversationCount = useStore(store, chatSelectors.conversationCount);
  const isStreaming = useStore(store, chatSelectors.isStreaming);

  return (
    <div>
      <span>{messageCount} messages</span>
      <span>{conversationCount} conversations</span>
      <span>{isStreaming ? 'Streaming' : 'Idle'}</span>
    </div>
  );
}

Parameterized selectors

Some selectors accept arguments. Pass them as additional arguments to useStore():

// Single message by ID
const message = useStore(store, chatSelectors.message, 'msg-42');

// Single conversation by ID
const conversation = useStore(store, chatSelectors.conversation, 'support');

// Typing users in a specific conversation
const typingUserIds = useStore(store, chatSelectors.typingUserIds, 'support');

// Typing users in the active conversation (no conversationId needed)
const activeTypingUserIds = useStore(
  store,
  chatSelectors.typingUserIdsForActiveConversation,
);

When to use direct store access

Use useChatStore() + chatSelectors when:

  • You need a derived value that no built-in hook provides (for example, message count, active conversation title).
  • You are building a metrics dashboard or debug panel.
  • You need to subscribe to store changes outside the React render cycle.
  • You want to combine multiple selectors into a single subscription.

For standard rendering, the built-in hooks (useChat(), useMessageIds(), useMessage(), and so on) are sufficient.

The demo below shows how to combine useChatStore() with chatSelectors to render live counts and streaming state:

Store escape hatch

The runtime stays headless, but advanced consumers can subscribe to exactly the slices they need.

Messages

4

Conversations

2

Active title

Support

Composer value

Track this with a custom selector.

Typing users

none

MUI Agent · sent

Welcome to the headless runtime demo.

Alice · sent

Show me the smallest possible chat setup.

MUI Agent · sent

The controlled API keeps public state array-first.

Sam · sent

That is the behavior we want to document.

Press Enter to start editing

Key takeaways

  • useChatStore() is the escape hatch for advanced store access.
  • chatSelectors provides memoized selectors for all store slices.
  • Combine with useStore() for React subscriptions or use the store directly for imperative access.
  • Prefer the built-in hooks for standard use cases—they wrap these selectors with a better developer experience.

See also

  • See Selectors for the full selector reference.
  • See Hooks for the convenience hooks that wrap these selectors.
  • See State and store for details on the normalized store internals.
  • See Selector-driven thread for the useMessageIds() + useMessage(id) pattern.

API