useStreamChat
The core React hook for streaming chat with pluggable transports.
useStreamChat manages the full lifecycle of a streaming chat: state, network requests, event parsing, cancellation, and event handling. It supports multiple transport strategies — direct SSE, background SSE, WebSocket, or a fully custom transport.
import { useStreamChat } from "@deltakit/react";Signature
function useStreamChat<
TPart extends { type: string } = ContentPart,
TEvent extends { type: string } = SSEEvent,
>(options: UseStreamChatOptions<TPart, TEvent>): UseStreamChatReturn<TPart>Options
transport
Type: "sse" | "background-sse" | "websocket" | ChatTransport<TPart, TEvent>
The transport strategy to use. Defaults to "sse".
// Direct SSE (default)
useStreamChat({ api: "/api/chat" });
// Background SSE — job-based with resumable event streams
useStreamChat({
transport: "background-sse",
transportOptions: {
backgroundSSE: {
startApi: "/api/jobs",
eventsApi: (id) => `/api/jobs/${id}/events`,
},
},
});
// WebSocket
useStreamChat({
transport: "websocket",
transportOptions: {
websocket: { url: "ws://localhost:8000/ws" },
},
});
// Custom transport
useStreamChat({
transport: {
start: ({ context, message }) => { /* ... */ },
resume: ({ context, runId }) => { /* ... */ },
},
});transportOptions
Type: TransportOptions<TEvent>
Configuration for built-in transports. Each transport reads from its own key:
transportOptions.sse— for the"sse"transporttransportOptions.backgroundSSE— for the"background-sse"transporttransportOptions.websocket— for the"websocket"transport
See the Background Task Streaming and WebSocket Chat recipes for full configuration details.
api
Type: string
The endpoint URL to POST messages to. Required when using the default "sse" transport (or use transportOptions.sse.api).
useStreamChat({ api: "/api/chat" });initialMessages
Type: Message<TPart>[]
Pre-populate the conversation, e.g. from a database or fromOpenAiAgents.
useStreamChat({
api: "/api/chat",
initialMessages: [
{ id: "1", role: "user", parts: [{ type: "text", text: "Hello" }] },
{ id: "2", role: "assistant", parts: [{ type: "text", text: "Hi!" }] },
],
});headers
Type: Record<string, string>
Extra headers merged into every fetch request. Content-Type: application/json is always included.
useStreamChat({
api: "/api/chat",
headers: {
Authorization: `Bearer ${token}`,
},
});body
Type: Record<string, unknown>
Extra fields merged into the POST body alongside message.
useStreamChat({
api: "/api/chat",
body: {
model: "gpt-4",
temperature: 0.7,
sessionId: "abc123",
},
});
// POST body: { message: "...", model: "gpt-4", temperature: 0.7, sessionId: "abc123" }onEvent
Type: (event: TEvent, helpers: EventHelpers<TPart>) => void
Custom handler for each SSE event. When provided, this replaces the default text_delta handling entirely.
useStreamChat({
api: "/api/chat",
onEvent: (event, { appendText, appendPart }) => {
switch (event.type) {
case "text_delta":
appendText(event.delta);
break;
case "tool_call":
appendPart({
type: "tool_call",
tool_name: event.tool_name,
argument: event.argument,
callId: event.call_id,
});
break;
}
},
});See EventHelpers for the full helpers API and Custom Event Handling for patterns.
onFinish
Type: (messages: Message<TPart>[]) => void
Called when the stream ends successfully. Receives the full message array.
useStreamChat({
api: "/api/chat",
onFinish: (messages) => {
// Persist to database, analytics, etc.
saveConversation(messages);
},
});onMessage
Type: (message: Message<TPart>) => void
Called when a new message is added. Fires for both user messages (immediately on send) and assistant messages (when the stream completes).
useStreamChat({
api: "/api/chat",
onMessage: (message) => {
console.log(`New ${message.role} message:`, message.id);
},
});onError
Type: (error: Error) => void
Called on fetch or stream errors. Abort errors (from calling stop()) are swallowed and do not trigger this callback.
useStreamChat({
api: "/api/chat",
onError: (error) => {
toast.error(error.message);
},
});Return Values
messages
Type: Message<TPart>[]
Chronological list of all messages. Updates in real-time during streaming.
isLoading
Type: boolean
true while streaming an assistant response. sendMessage is ignored while loading.
error
Type: Error | null
The most recent error, or null. Reset to null on each new sendMessage call.
runId
Type: string | null
The current resumable run id, if the active transport exposes one. Used by background-sse and websocket transports for reconnection. Reset to null when the stream completes.
sendMessage
Type: (text: string) => void
Send a user message and begin streaming the assistant response. Creates both a user message and an empty assistant message, then starts the SSE stream. Ignored if already loading.
stop
Type: () => void
Abort the current in-flight stream. Safe to call when not streaming. When cancelUrl (websocket) or cancelApi (background-sse) is configured, stop also sends a POST request to the backend to cancel the running job server-side.
setMessages
Type: Dispatch<SetStateAction<Message<TPart>[]>>
Direct React state setter. Use for programmatic manipulation:
const { setMessages } = useStreamChat({ api: "/api/chat" });
// Clear conversation
setMessages([]);
// Remove last message
setMessages((prev) => prev.slice(0, -1));Generic Types
The hook accepts two type parameters for full type safety with custom content:
useStreamChat<TPart, TEvent>(options)TPart. Shape of content parts in messages. Defaults toContentPart. Must have{ type: string }.TEvent. Shape of SSE events from the server. Defaults toSSEEvent. Must have{ type: string }.
interface StatusPart {
type: "status";
status: "thinking" | "searching";
}
interface StatusEvent {
type: "status_update";
status: "thinking" | "searching";
}
const { messages } = useStreamChat<
ContentPart | StatusPart,
SSEEvent | StatusEvent
>({
api: "/api/chat",
onEvent: (event, helpers) => {
// event is SSEEvent | StatusEvent - fully typed
},
});
// messages is Message<ContentPart | StatusPart>[]Lifecycle
sendMessage("Hello")is called- A user message and an empty assistant message are appended to state
onMessagefires for the user messageisLoadingbecomestrue- The active transport starts (SSE fetch, background job, or WebSocket frame)
- Each event is passed to
onEvent(or the default handler) - State updates trigger re-renders with streaming content
- When the stream ends:
onMessagefires for the assistant message,onFinishfires isLoadingbecomesfalse
Resume Lifecycle (background-sse / websocket)
When a persisted runId is found on mount:
- The transport's
resumemethod is called with the run id isLoadingbecomestrue- Events stream from where the client left off
- The lifecycle continues from step 6 above
Cleanup
The hook automatically closes any in-flight transport (aborts fetch, closes WebSocket) when the component unmounts.