The useChat Hook
The useChat hook is the primary way to build chat interfaces with the Vercel AI SDK.
It handles message state, streaming responses, loading states, and user input management automatically.
What useChat Provides
- messages: Array of chat messages with automatic updates
- input: Current input field value
- handleInputChange: Input change handler
- handleSubmit: Form submit handler
- isLoading: Loading state during generation
- error: Error state if something goes wrong
Basic Usage
'use client';
import { useChat } from 'ai/react';
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
<div className="flex flex-col h-screen">
{/* Message List */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((message) => (
<div
key={message.id}
className={`p-4 rounded-lg ${
message.role === 'user'
? 'bg-blue-100 ml-auto max-w-[80%]'
: 'bg-gray-100 mr-auto max-w-[80%]'
}`}
>
<p className="font-semibold">
{message.role === 'user' ? 'You' : 'AI'}
</p>
<p className="whitespace-pre-wrap">{message.content}</p>
</div>
))}
</div>
{/* Input Form */}
<form onSubmit={handleSubmit} className="p-4 border-t">
<div className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Type a message..."
className="flex-1 p-2 border rounded-lg"
/>
<button
type="submit"
className="px-4 py-2 bg-blue-500 text-white rounded-lg"
>
Send
</button>
</div>
</form>
</div>
);
}
Server-Side API Route
// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4-turbo'),
system: 'You are a helpful assistant.',
messages,
});
return result.toDataStreamResponse();
}
All useChat Options
const {
messages, // Message array
input, // Current input value
handleInputChange, // Input onChange handler
handleSubmit, // Form onSubmit handler
isLoading, // Loading state
error, // Error state
reload, // Regenerate last AI response
stop, // Stop current generation
setMessages, // Manually set messages
append, // Append a message programmatically
setInput, // Manually set input value
} = useChat({
api: '/api/chat', // API endpoint (default: '/api/chat')
id: 'my-chat', // Unique ID for multiple chats
initialMessages: [], // Pre-populate messages
initialInput: '', // Initial input value
// Callbacks
onResponse: (response) => {
console.log('Response started:', response);
},
onFinish: (message) => {
console.log('Message complete:', message);
},
onError: (error) => {
console.error('Error:', error);
},
// Additional options
headers: { 'X-Custom-Header': 'value' },
body: { customField: 'value' }, // Extra data to send
sendExtraMessageFields: true, // Include extra message fields
});
Loading States
'use client';
import { useChat } from 'ai/react';
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
return (
<div>
{messages.map((m) => (
<div key={m.id}>{m.content}</div>
))}
{isLoading && (
<div className="flex items-center gap-2 text-gray-500">
<div className="animate-spin h-4 w-4 border-2 border-blue-500 rounded-full border-t-transparent" />
AI is typing...
</div>
)}
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={handleInputChange}
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send'}
</button>
</form>
</div>
);
}
Stop and Reload
'use client';
import { useChat } from 'ai/react';
export default function Chat() {
const {
messages,
input,
handleInputChange,
handleSubmit,
isLoading,
stop,
reload,
} = useChat();
return (
<div>
{messages.map((m) => (
<div key={m.id}>{m.content}</div>
))}
<div className="flex gap-2">
{isLoading ? (
<button onClick={stop} className="text-red-500">
Stop generating
</button>
) : (
messages.length > 0 && (
<button onClick={() => reload()} className="text-blue-500">
Regenerate response
</button>
)
)}
</div>
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button type="submit">Send</button>
</form>
</div>
);
}
Programmatic Message Control
'use client';
import { useChat } from 'ai/react';
export default function Chat() {
const { messages, append, setMessages } = useChat();
// Add a message programmatically
const sendPredefinedMessage = () => {
append({
role: 'user',
content: 'Tell me a joke!',
});
};
// Clear all messages
const clearChat = () => {
setMessages([]);
};
// Edit the last message and regenerate
const editLastMessage = (newContent: string) => {
const newMessages = messages.slice(0, -1);
setMessages(newMessages);
append({
role: 'user',
content: newContent,
});
};
return (
<div>
{messages.map((m) => (
<div key={m.id}>{m.content}</div>
))}
<button onClick={sendPredefinedMessage}>Tell me a joke</button>
<button onClick={clearChat}>Clear chat</button>
</div>
);
}
Custom Headers and Body
'use client';
import { useChat } from 'ai/react';
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
// Send additional data with each request
body: {
userId: 'user-123',
sessionId: 'session-456',
},
// Custom headers
headers: {
'X-Custom-Header': 'my-value',
},
});
return (
<div>
{messages.map((m) => (
<div key={m.id}>{m.content}</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button type="submit">Send</button>
</form>
</div>
);
}
Key Takeaways
- •
useChathandles all chat state and streaming automatically - • Pair it with a server-side API route using
streamText - • Use
isLoadingfor loading indicators - •
stop()cancels generation,reload()regenerates - •
append()andsetMessages()for programmatic control
Learn More
-
useChat Documentation →
Complete API reference for the useChat hook.
-
useChat with Tool Calling →
Learn how to use tools with the chat interface.