@anvia/react

React hooks and client transports for Anvia chat applications.

@anvia/react provides useChat, transport abstractions, and stream parsers for building chat UIs that connect to an Anvia server endpoint.

Install

pnpm add @anvia/react

Peer dependency: react >= 18.

Quick Start

import { useChat } from "@anvia/react";

export function Chat() {
  const chat = useChat({ endpoint: "/api/chat" });

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        void chat.send();
      }}
    >
      <div>{chat.text}</div>
      <input
        value={chat.input}
        onChange={(event) => chat.setInput(event.target.value)}
      />
      <button disabled={chat.status === "streaming"}>Send</button>
    </form>
  );
}

This connects to a server route that returns a JSONL or SSE event stream (see @anvia/server).

Configuration

useChat accepts these options:

OptionTypeDefaultPurpose
endpointstring | URL--URL for the default fetch transport
transportEventTransport--Custom transport (overrides endpoint)
format"jsonl" | "sse""jsonl"Stream format for the default transport
initialMessagesChatMessage[][]Pre-populated message history
createRequestfunction--Custom request builder
eventToDeltafunction--Extract text delta from an event
eventToFinalfunction--Extract final text from an event
onEventfunction--Callback for each streamed event
onErrorfunction--Error callback

useChat Return Value

type UseChatResult<TEvent, TMessage> = {
  messages: TMessage[];       // conversation history
  events: TEvent[];           // raw streamed events
  input: string;              // current input value
  setInput(input: string): void;
  send(input?: string): Promise<void>;
  stop(): void;               // abort the current stream
  reset(messages?: TMessage[]): void;
  status: "idle" | "streaming" | "error";
  error: unknown;
  text: string;               // accumulated assistant text
};

Transports

The transport layer is the boundary between your UI and the network. The default fetch transport works for most cases.

import { createFetchTransport, createChatTransport } from "@anvia/react";

// Generic fetch transport
const transport = createFetchTransport({
  endpoint: "/api/chat",
  format: "jsonl",
});

// Chat-specific transport (POST JSON by default)
const transport = createChatTransport({
  endpoint: "/api/chat",
  format: "sse",
});

Pass a custom transport to useChat when you need WebSocket, local, or other non-HTTP transports.

Stream Parsers

Use these directly when you need raw stream access without useChat:

import { readJsonlStream, readSseStream, fetchEventStream } from "@anvia/react";

// Parse a ReadableStream as JSONL
for await (const event of readJsonlStream(stream)) {
  console.log(event);
}

// Fetch and parse a stream in one call
for await (const event of fetchEventStream("/api/chat", {
  method: "POST",
  body: JSON.stringify({ message: "Hello" }),
  format: "jsonl",
})) {
  console.log(event);
}

EventTransport Interface

All transports implement this interface:

type EventTransport<TRequest, TEvent> = {
  send(request: TRequest, options?: TransportOptions): AsyncIterable<TEvent>;
};

This makes useChat transport-agnostic: swap JSONL for SSE, HTTP for WebSocket, or a mock for testing without changing the hook.

Server Pairing

Pair @anvia/react with @anvia/server on the backend:

// Server (Next.js, Hono, Express, etc.)
import { createEventStream } from "@anvia/server";

return createEventStream(agent.prompt(message).stream(), {
  format: "jsonl",
});
// Client
const chat = useChat({ endpoint: "/api/chat", format: "jsonl" });