Chat - Controlled state
Manage messages, conversations, and composer state externally using the controlled and uncontrolled patterns.
The controlled state model lets the parent component own messages, conversations, and composer state while the runtime still streams and normalizes them internally. All four public state models are owned by the parent component while the runtime still streams, normalizes, and derives selectors internally.
Key concepts
The four controlled models
Each model has a controlled prop, a change callback, and an uncontrolled default:
| Model | Controlled prop | Change callback |
|---|---|---|
| Messages | messages |
onMessagesChange |
| Conversations | conversations |
onConversationsChange |
| Active conversation | activeConversationId |
onActiveConversationChange |
| Composer value | composerValue |
onComposerValueChange |
Wiring controlled state
Pass your React state directly to ChatProvider:
<ChatProvider
adapter={adapter}
conversations={conversations}
onConversationsChange={setConversations}
activeConversationId={activeConversationId}
onActiveConversationChange={setActiveConversationId}
messages={messages}
onMessagesChange={setMessages}
composerValue={composerValue}
onComposerValueChange={setComposerValue}
>
<ControlledStateChat />
</ChatProvider>
When to use controlled state
Use controlled state when you need to:
- sync chat state with a global store (Redux, Zustand, and so on)
- persist messages across navigation or page reloads
- drive the conversation list from an external data source
- coordinate the composer value with external UI (for example, slash commands)
The demo below shows controlled state wired through ChatProvider:
Controlled headless state
2
2
product
Document the controlled models.
The controlled API keeps public state array-first.
That is the behavior we want to document.
Key takeaways
- Controlled state lets you own the source of truth while the runtime handles streaming and normalization.
- Switch from uncontrolled to controlled at any time without changing the runtime model.
- The
onMessagesChangecallback fires with the full array after every update, including streaming deltas.
See also
- State and store for details on the
ChatProviderprops reference. - Selector-driven thread for details on efficient rendering with controlled state.
- Conversation history for details on adapter-driven conversation loading.