TechLead
Lesson 3 of 8
5 min read
AI SDK

useChat Hook

Build conversational AI interfaces with the useChat hook for chat applications

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

  • useChat handles all chat state and streaming automatically
  • • Pair it with a server-side API route using streamText
  • • Use isLoading for loading indicators
  • stop() cancels generation, reload() regenerates
  • append() and setMessages() for programmatic control

Learn More

Continue Learning