Appearance
React & Next.js
Use @sendsar/chat-uikit-react with @sendsar/chat-sdk-javascript for React and Next.js apps.
New? Follow the Quick start. Your app connects with a session token from your backend — never your API key. Details: Server setup.
Using plain HTML or a non-React framework? See HTML & vanilla JS.
Need voice or video? Optional add-on — Voice & video calls (@sendsar/chat-sdk-javascript + media kit (planned) + media UI kit (planned)).
Common mistakes
| Problem | Fix |
|---|---|
404 when minting a token on the server | Call POST /users before POST /auth/token — see Server setup |
API key in NEXT_PUBLIC_* or client bundle | Mint tokens on the server; pass only the session JWT to SendsarProvider |
| Chat UI stale after token refresh | SendsarProvider reconnects automatically — avoid storing the JWT in client state alone |
| Room list shows wrong data | Let the JWT scope rooms to the logged-in user; create rooms on your server with the API key |
Full auth reference: Authentication.
Install
bash
npm install @sendsar/chat-uikit-react @sendsar/chat-sdk-javascriptPeer dependency: React 18+.
Next.js — add both packages to transpilePackages:
typescript
// next.config.ts
const nextConfig = {
transpilePackages: ["@sendsar/chat-sdk-javascript", "@sendsar/chat-uikit-react"],
};
export default nextConfig;Provider and connect
Wrap your chat UI once. The provider calls your session route and connects to the Sendsar API.
tsx
"use client";
import { SendsarProvider, useSendsar } from "@sendsar/chat-uikit-react";
export function ChatShell({ children }: { children: React.ReactNode }) {
return (
<SendsarProvider fetchSession={() => fetch("/api/chat/session")}>
{children}
</SendsarProvider>
);
}
export function ChatApp() {
const { status, client, session, error } = useSendsar();
if (status === "loading") return <p>Connecting…</p>;
if (status === "offline") return <p>Chat unavailable</p>;
if (status === "error") return <p>{error}</p>;
if (!client || !session) return null;
return <YourChatUI client={client} session={session} />;
}SendsarProvider handles:
- Fetching
/api/chat/session connect()withtokenandapiUrl- Token refresh before expiry (immediately if the JWT lifetime is shorter than the refresh window)
- Disconnect on page hide
You can pass an inline fetchSession function — the provider keeps the latest callback without reconnecting on every render.
Use the client in a room
tsx
import { useEffect, useState } from "react";
import type { Message } from "@sendsar/chat-sdk-javascript";
import { useSendsar } from "@sendsar/chat-uikit-react";
function RoomThread({ roomId }: { roomId: string }) {
const { client, session } = useSendsar();
const [messages, setMessages] = useState<Message[]>([]);
useEffect(() => {
if (!client || !session) return;
client.joinRoom({ roomId });
void client.getMessages(roomId, { limit: 50 }).then(({ messages: page }) => {
setMessages([...page].reverse());
});
const off = client.on("new-message", (msg) => {
if (msg.roomId !== roomId) return;
setMessages((prev) => [...prev, msg]);
if (msg.senderId !== session.chatUserId) {
void client.markRoomRead(roomId, msg.id);
}
});
return () => {
off();
client.leaveRoom(roomId);
};
}, [client, session, roomId]);
// render messages + composer …
}Or use createRoomSubscription from @sendsar/chat-sdk-javascript — see HTML & vanilla JS.
One active room per WebSocket
Call client.joinRoom({ roomId }) when the user opens a conversation. The gateway keeps one joined room at a time for typing and presence; messages for all rooms still arrive. See Realtime constraints.
Hooks
Typing indicator
tsx
import { useComposerTyping } from "@sendsar/chat-uikit-react";
import type { SendsarClient } from "@sendsar/chat-sdk-javascript";
function Composer({ client, roomId }: { client: SendsarClient; roomId: string }) {
const { onComposerValueChange } = useComposerTyping(client, roomId);
return (
<textarea
onChange={(e) => onComposerValueChange(e.target.value)}
placeholder="Message…"
/>
);
}Online presence
tsx
import { useTenantPresence } from "@sendsar/chat-uikit-react";
import type { SendsarClient } from "@sendsar/chat-sdk-javascript";
function Inbox({ client }: { client: SendsarClient }) {
const onlineUserIds = useTenantPresence(client);
// onlineUserIds.has(peerId) → show "Active now"
}Exports
| Export | Description |
|---|---|
SendsarProvider | Session, connect, token refresh |
useSendsar | { status, client, session, error } |
useComposerTyping | Debounced outgoing typing |
useTenantPresence | Set of online user IDs |
SendsarSession | Session type (displayLabel alias) |
Low-level client methods (sendMessage, callInvite, etc.) come from @sendsar/chat-sdk-javascript via useSendsar().client.
Optional: voice & video
| Package | Role |
|---|---|
media kit (planned) | WebRTC media |
media UI kit (planned) | SendsarCallProvider, call hooks |
Wrap your app with SendsarCallProvider inside SendsarProvider. Full guide: Voice & video calls.
Next steps
- Voice & video calls — optional signaling + media packages
- UI Kit
- API Reference