Chat - Selector-driven thread
Render large custom threads efficiently by subscribing rows to individual message IDs.
Use this pattern to render large custom threads efficiently when subscribing the entire list to every message change would be too costly. Instead of subscribing the entire list to every message change, each row subscribes only to its own message record.
Key concepts
ID list and row subscription pattern
The parent component calls useMessageIds() to get the ordered list of message IDs.
Each row component calls useMessage(id) to subscribe to its own message:
function Thread() {
const messageIds = useMessageIds();
return (
<div>
{messageIds.map((id) => (
<MessageRow key={id} id={id} />
))}
</div>
);
}
const MessageRow = React.memo(function MessageRow({ id }: { id: string }) {
const message = useMessage(id);
if (!message) return null;
return (
<div>{message.parts[0]?.type === 'text' ? message.parts[0].text : null}</div>
);
});
Why this matters
The store keeps messages in a normalized shape (messageIds + messagesById).
When a single message updates during streaming:
messageIdsstays reference-equal because the ID list did not change.- Only the
messagesByIdentry for the updated message changes. useMessage(id)on the updated row triggers a re-render.- All other rows stay untouched.
For a thread with 100 messages where one is streaming, only one component re-renders per delta—not 100.
Conversation-level selectors
The same pattern applies to conversations, as shown below:
const conversations = useConversations();
const conversation = useConversation('selectors');
The demo below shows the selector-driven thread pattern end to end:
Selector-driven thread
Update one controlled message from the parent to see only the matching row rerender.
MUI Agent
Row 1 is subscribed independently.
Alice
Row 2 is subscribed independently.
MUI Agent
Row 3 is subscribed independently.
Alice
Row 4 is subscribed independently.
MUI Agent
Row 5 is subscribed independently.
Alice
Row 6 is subscribed independently.
MUI Agent
Row 7 is subscribed independently.
Alice
Row 8 is subscribed independently.
MUI Agent
Row 9 is subscribed independently.
Alice
Row 10 is subscribed independently.
MUI Agent
Row 11 is subscribed independently.
Alice
Row 12 is subscribed independently.
MUI Agent
Row 13 is subscribed independently.
Alice
Row 14 is subscribed independently.
Key takeaways
useMessageIds()+useMessage(id)is the recommended pattern for threads with more than a handful of messages.- The normalized store ensures stable references—only changed data triggers re-renders.
- Wrap row components in
React.memo()for maximum efficiency. useConversations()anduseConversation(id)follow the same pattern for conversation lists.
See also
- Selectors for details on the full selector API and custom subscriptions.
- Hooks for details on all available hooks.
- Advanced store access for details on custom selectors with
useChatStore().