What fully controlled means
Why choose this mode
Use ChatInterface when your app already has a state architecture, custom persistence rules, or backend orchestration that should stay centralized.
Full working example
App.tsx
import React from 'react';
import { ChatInterface } from '@jazzmine-ui/react';
import type { Chat, Context, Message } from '@jazzmine-ui/react';
import '@jazzmine-ui/react/styles';
export function App() {
// App-owned state: ChatInterface only renders what you pass.
const [messages, setMessages] = React.useState<Message[]>([]);
const [chats] = React.useState<Chat[]>([]);
const [activeChatId, setActiveChatId] = React.useState<string>();
const [isLoading, setIsLoading] = React.useState(false);
const onSend = async (text: string, contexts?: Context[]) => {
setMessages((prev) => [
...prev,
{ id: crypto.randomUUID(), text, sender: 'user', timestamp: new Date().toISOString() },
]);
setIsLoading(true);
try {
// Forward optional contexts to backend when available.
console.log('Forward selected contexts to backend:', contexts);
const reply = `Echo: ${text}`;
setMessages((prev) => [
...prev,
{
id: crypto.randomUUID(),
text: reply,
sender: 'assistant',
timestamp: new Date().toISOString(),
},
]);
} finally {
setIsLoading(false);
}
};
const onNewChat = () => {
console.log('Create a new chat in your backend, then update chats state.');
};
const onSelectChat = (chatId: string) => {
setActiveChatId(chatId);
console.log('Load messages for chat:', chatId);
};
const onDeleteChat = (chatId: string) => {
console.log('Delete chat in backend:', chatId);
if (activeChatId === chatId) {
setActiveChatId(undefined);
setMessages([]);
}
};
return (
<div style={{ height: '100vh' }}>
<ChatInterface
chats={chats} /* Required: conversations shown in sidebar */
activeChatId={activeChatId} /* Optional: currently active conversation id */
messages={messages} /* Required: feed data for the main panel */
onSend={onSend} /* Required: composer send action */
onNewChat={onNewChat} /* Required: new-chat action */
onSelectChat={onSelectChat} /* Required: sidebar selection action */
onDeleteChat={onDeleteChat} /* Optional: enables delete action */
isLoading={isLoading} /* Optional: assistant loading state */
defaultMessage="Welcome! Ask me anything to get started." /* Optional: empty feed message */
/>
</div>
);
}Context forwarding with onSend
The send callback receives selected context snippets as the second argument. Forward those contexts to your backend if your model supports retrieval-augmented or grounded responses.
ChatInterface props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| messages | Message[] | Yes | - | Current message list. |
| chats | Chat[] | Yes | - | Sidebar conversation list. |
| onSend | (text: string, contexts?: Context[]) => void | Yes | - | Send handler; receives selected context snippets when present. |
| onNewChat | () => void | Yes | - | New chat action. |
| onSelectChat | (chatId: string) => void | Yes | - | Active chat selection. |
| activeChatId | string | No | - | Active conversation id. |
| assistantName | string | No | 'AI Assistant' | Assistant display name. |
| onRenameChat | (chatId: string, newTitle?: string) => void | No | - | Rename conversation action. |
| onDeleteChat | (chatId: string) => void | No | - | Delete conversation action. |
| onSearchClick | () => void | No | - | Opens external conversation search flow. |
| onLoadMore | () => void | No | - | Load-more trigger for infinite conversation lists. |
| hasMore | boolean | No | false | Indicates additional chat pages are available. |
| isLoadingChats | boolean | No | false | Sidebar list loading indicator. |
| isSearchOpen | boolean | No | false | Controls sidebar search visibility. |
| searchQuery | string | No | '' | Sidebar search query value. |
| onSearchQueryChange | (query: string) => void | No | - | Sidebar search input handler. |
| isLoading | boolean | No | false | Message send/load state. |
| loadingText | string | No | '' | Assistant status text while loading. |
| loadingVariant | 'text-and-dots' | 'dots-only' | No | 'text-and-dots' | Loading indicator style. |
| placeholder | string | No | 'Type your message...' | Input placeholder text. |
| className | string | No | '' | Root class override. |
| defaultMessage | string | No | '' | Empty-state assistant message. |