useChat on the client

The client is where the streaming magic is visible. useChat from @ai-sdk/react handles the fetch, the stream parsing, and the message state so you can focus on the UI.

useChat lifecycle

What happens between a keystroke and a rendered token.

src/app/page.tsx
tsx
'use client';

import { useChat } from '@ai-sdk/react';

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    api: '/api/chat',
  });

  return (
    <div className="flex flex-col gap-3">
      {messages.map((m) => (
        <div key={m.id} className={m.role === 'user' ? 'text-right' : 'text-left'}>
          <span className="font-mono text-xs opacity-60">{m.role}</span>
          <div>{m.content}</div>
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} placeholder="Ask something..." />
      </form>
    </div>
  );
}

useChat owns the message list, the input state, the submission, and the streaming updates. You render, you do not orchestrate.

You can, but you would reinvent quite a lot. useChat normalizes UI messages, handles streaming parts as they arrive, deduplicates ids, cancels in-flight requests, and exposes status and error states. Ship your own only if you have a reason, because you will.

Quiz: Quiz

Loading practice…