Skip to content

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

ProblemFix
404 when minting a token on the serverCall POST /users before POST /auth/token — see Server setup
API key in NEXT_PUBLIC_* or client bundleMint tokens on the server; pass only the session JWT to SendsarProvider
Chat UI stale after token refreshSendsarProvider reconnects automatically — avoid storing the JWT in client state alone
Room list shows wrong dataLet 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-javascript

Peer 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() with token and apiUrl
  • 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

ExportDescription
SendsarProviderSession, connect, token refresh
useSendsar{ status, client, session, error }
useComposerTypingDebounced outgoing typing
useTenantPresenceSet of online user IDs
SendsarSessionSession type (displayLabel alias)

Low-level client methods (sendMessage, callInvite, etc.) come from @sendsar/chat-sdk-javascript via useSendsar().client.

Optional: voice & video

PackageRole
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

Sendsar — headless chat for B2B platforms