IntegrationReact

Build AI Chat Components in React with Multiple LLM Models

Create streaming chat UIs in React that can switch between GPT-5.2, Claude, Gemini, and 6 more models. One API, one hook, infinite possibilities.

You only pay credits per request. No monthly subscription. Paid credits never expire.

Replace multiple AI subscriptions with one wallet that includes routing, failover, and optimization.

Why teams start here first
No monthly subscription
Pay-as-you-go credits
Start with trial credits, then buy only what you consume.
Failover safety
Production-ready routing
Auto fallback across providers when latency, quality, or reliability changes.
Data control
Your policy, your choice
BYOK and zero-retention mode keep training and storage scope explicit.
Single API experience
One key, multi-provider access
Use Chat/Compare/Blend/Judge/Failover from one dashboard.
Quick start
npm install llmwise

Full example

React
// hooks/useChat.ts — Custom React hook for streaming LLMWise chat
import { useState, useCallback, useRef } from "react";

interface Message {
  id: string;
  role: "user" | "assistant";
  content: string;
}

interface UseChatOptions {
  model?: string;
  apiUrl?: string;
}

export function useChat({ model = "auto", apiUrl = "/api/chat" }: UseChatOptions = {}) {
  const [messages, setMessages] = useState<Message[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const abortRef = useRef<AbortController | null>(null);

  const sendMessage = useCallback(async (content: string) => {
    const userMsg: Message = { id: crypto.randomUUID(), role: "user", content };
    const assistantMsg: Message = { id: crypto.randomUUID(), role: "assistant", content: "" };

    setMessages((prev) => [...prev, userMsg, assistantMsg]);
    setIsLoading(true);
    abortRef.current = new AbortController();

    try {
      const res = await fetch(apiUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          model,
          messages: [...messages, userMsg].map(({ role, content }) => ({ role, content })),
          stream: true,
        }),
        signal: abortRef.current.signal,
      });

      const reader = res.body!.getReader();
      const decoder = new TextDecoder();
      let buffer = "";

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });

        let idx = 0;
        while ((idx = buffer.indexOf("\n\n")) !== -1) {
          const raw = buffer.slice(0, idx);
          buffer = buffer.slice(idx + 2);

          for (const line of raw.split("\n")) {
            if (!line.startsWith("data: ")) continue;
            const data = line.slice(6);
            if (data === "[DONE]") return;

            const parsed = JSON.parse(data);
            if (parsed.error) throw new Error(parsed.error);

            const delta = parsed.delta ?? "";
            if (delta) {
              setMessages((prev) =>
                prev.map((m) =>
                  m.id === assistantMsg.id ? { ...m, content: m.content + delta } : m
                )
              );
            }

            if (parsed.event === "done") return;
          }
        }
      }
    } catch (err) {
      if ((err as Error).name !== "AbortError") console.error(err);
    } finally {
      setIsLoading(false);
    }
  }, [messages, model, apiUrl]);

  const stop = useCallback(() => abortRef.current?.abort(), []);

  return { messages, sendMessage, isLoading, stop };
}

// ChatApp.tsx — Example component using the hook
import { useState } from "react";
import { useChat } from "./hooks/useChat";

const MODELS = ["auto", "gpt-5.2", "claude-sonnet-4.5", "gemini-3-flash", "deepseek-v3"];

export function ChatApp() {
  const [model, setModel] = useState("auto");
  const [input, setInput] = useState("");
  const { messages, sendMessage, isLoading, stop } = useChat({ model });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (!input.trim()) return;
    sendMessage(input);
    setInput("");
  };

  return (
    <div style={{ maxWidth: 640, margin: "0 auto", padding: 16 }}>
      <select value={model} onChange={(e) => setModel(e.target.value)}>
        {MODELS.map((m) => <option key={m} value={m}>{m}</option>)}
      </select>
      <div style={{ minHeight: 400, overflowY: "auto" }}>
        {messages.map((m) => (
          <div key={m.id} style={{ marginBottom: 12 }}>
            <strong>{m.role === "user" ? "You" : model}:</strong>
            <p>{m.content}</p>
          </div>
        ))}
      </div>
      <form onSubmit={handleSubmit} style={{ display: "flex", gap: 8 }}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Type a message..."
          style={{ flex: 1, padding: 8 }}
        />
        {isLoading ? (
          <button type="button" onClick={stop}>Stop</button>
        ) : (
          <button type="submit">Send</button>
        )}
      </form>
    </div>
  );
}
Evidence snapshot

React integration overview

Everything you need to integrate LLMWise's multi-model API into your React project.

Setup steps
5
to first API call
Features
8
capabilities included
Models available
9
via single endpoint
Starter credits
40
trial 7 days · paid credits never expire

What you get

+Custom useChat hook with streaming, abort, and model switching
+9 models from 7 providers — swap models from a dropdown
+Real-time streaming via Server-Sent Events parsed in the browser
+Works with any React framework: Vite, Create React App, Remix, Gatsby
+Built-in abort controller for canceling in-flight requests
+No heavy framework dependencies — just fetch + SSE parsing in React
+Built-in failover ensures your UI stays responsive during provider outages
+TypeScript-first with full type definitions for messages and options

Step-by-step integration

1Set up a backend proxy for your API key

Never expose your LLMWise API key in client-side code. Create a thin backend endpoint (Express, Fastify, or any server) that forwards requests to LLMWise and streams the response back to the browser.

// server.ts — Minimal Express proxy
	import express from "express";
	import { LLMWise } from "llmwise";
	
	const app = express();
	app.use(express.json());
	
	const client = new LLMWise(process.env.LLMWISE_API_KEY!);
	
	app.post("/api/chat", async (req, res) => {
	  const { messages, model = "auto" } = req.body;
	
	  res.setHeader("Content-Type", "text/event-stream");
	  res.setHeader("Cache-Control", "no-cache");
	
	  for await (const ev of client.chatStream({ model, messages })) {
	    res.write("data: " + JSON.stringify(ev) + "\n\n");
	    if (ev.event === "done") break;
	    if ((ev as any).error) break;
	  }
	  res.write("data: [DONE]\n\n");
	  res.end();
	});
	
	app.listen(3001);
2Create a custom useChat hook

Build a React hook that manages message state, streams responses using the Fetch API ReadableStream, and supports aborting in-flight requests. This gives you full control without depending on framework-specific libraries.

// See the full useChat hook in the code example above.
// Key features: message state, SSE parsing, AbortController, model parameter.
3Build the chat component

Use the useChat hook in a React component. Add a model selector to let users switch between models, a message list with auto-scroll, and a form with a submit handler.

import { useChat } from "./hooks/useChat";

export function Chat() {
  const { messages, sendMessage, isLoading } = useChat({ model: "claude-sonnet-4.5" });
  const [input, setInput] = useState("");

  return (
    <div>
      {messages.map((m) => (
        <div key={m.id}>
          <strong>{m.role}:</strong> {m.content}
        </div>
      ))}
      <form onSubmit={(e) => { e.preventDefault(); sendMessage(input); setInput(""); }}>
        <input value={input} onChange={(e) => setInput(e.target.value)} />
        <button disabled={isLoading}>Send</button>
      </form>
    </div>
  );
}
4Add model switching

Store the selected model in component state and pass it to the useChat hook. When the user picks a different model, the next message will be routed to that model automatically.

const [model, setModel] = useState("gpt-5.2");
const { messages, sendMessage } = useChat({ model });

// Model selector dropdown
<select value={model} onChange={(e) => setModel(e.target.value)}>
  <option value="gpt-5.2">GPT-5.2</option>
  <option value="claude-sonnet-4.5">Claude Sonnet 4.5</option>
  <option value="gemini-3-flash">Gemini 3 Flash</option>
  <option value="deepseek-v3">DeepSeek V3</option>
</select>
5Handle loading states and cancellation

The useChat hook exposes isLoading and stop(). Use isLoading to show a typing indicator or disable the input, and wire stop() to a cancel button so users can abort long-running generations.

const { messages, sendMessage, isLoading, stop } = useChat({ model });

// Show loading indicator
{isLoading && <div className="typing-indicator">Generating...</div>}

// Cancel button
{isLoading && <button onClick={stop}>Cancel</button>}

Common questions

Can I use LLMWise directly from the browser without a backend?
You should not expose your API key in client-side JavaScript. Instead, create a lightweight backend proxy that holds the API key and forwards requests to LLMWise. This takes about 10 lines of Express code and keeps your credentials secure.
Does the React integration work with server-side rendering (SSR)?
The chat hook is client-side only since it manages browser state and streaming. For SSR frameworks like Next.js or Remix, use the hook in a client component and handle initial data loading on the server separately. The hook will hydrate on the client without issues.
How do I add markdown rendering to streamed AI responses?
Install a markdown library like react-markdown and wrap the message content in a Markdown component. Since the content updates incrementally during streaming, the markdown will render progressively as tokens arrive. Add syntax highlighting with rehype-highlight for code blocks.
Can I use LLMWise with React Native for mobile apps?
Yes. The useChat hook uses the Fetch API which is available in React Native. You will need a backend proxy just like a web app. For streaming, use the same ReadableStream approach or fall back to non-streaming requests if your React Native version does not support ReadableStream.

One wallet, enterprise AI controls built in

You only pay credits per request. No monthly subscription. Paid credits never expire.

Replace multiple AI subscriptions with one wallet that includes routing, failover, and optimization.

Chat, Compare, Blend, Judge, MeshPolicy routing + replay labFailover without extra subscriptions