React Chat SDK

Build your own user-facing conversation UI with headless React hooks.

Last reviewed

React Chat SDK

Use the React Chat SDK when you want to show getuserfeedback.com conversations inside your own app. You own the UI; getuserfeedback.com keeps the conversation history.

This guide builds a React conversation view that lists conversations, opens a conversation, loads older messages, and sends a message.

How authentication works

Chat requests always use a JWT bearer token created by your app.

Before you add the SDK, configure your backend or auth provider to mint a JWT for the signed-in user. That token must be intended for your getuserfeedback.com app:

aud: https://getuserfeedback.com/v1/apps/{appId}sub: the stable identity for this user in your app

The default setup uses sub as the user's stable app identity. If your getuserfeedback.com app uses a custom JWT claim mapping, configure one primary signed claim for chat, such as your app user id, account id, or another customer-owned identity value.

Your React app should fetch that token from your own auth flow, then pass it to the provider. The SDK sends the token on each API request:

Authorization: Bearer <token>

getuserfeedback.com verifies the token and uses the signed app identity to decide which conversations to return. See User authentication for issuer, JWKS, audience, and claim setup.

1. Install

npm install @getuserfeedback/chat-react

Requires react >= 18.

The package is headless and isomorphic. It can run in React DOM, React Native, and most non-browser React environments that provide fetch.

2. Add the provider

Wrap the part of your app that renders chat:

TypeScriptchat-provider.tsx
import { GetUserFeedbackChatProvider } from "@getuserfeedback/chat-react";import type { ReactNode } from "react";export function ChatProvider({children,token,}: {children: ReactNode;token: string;}) {return (<GetUserFeedbackChatProviderclientOptions={{auth: {jwt: {token,},},}}>{children}</GetUserFeedbackChatProvider>);}

The provider creates the chat client and makes it available to all hooks below it. When your auth provider refreshes the token, render the provider with the new token.

The default API base URL is https://api.getuserfeedback.com/v1.

3. List conversations

TypeScriptconversation-list.tsx
import { useConversations } from "@getuserfeedback/chat-react";export function ConversationList({onSelect,}: {onSelect: (conversationId: string) => void;}) {const { conversations, hasMore, loadMore, status } = useConversations();if (status === "loading") {return <p>Loading conversations...</p>;}return (<div>{conversations.length === 0 ? <p>No conversations yet.</p> : null}{conversations.map((conversation) => (<buttonkey={conversation.id}onClick={() => onSelect(conversation.id)}type="button">{conversation.latestMessage?.content.type === "text"? conversation.latestMessage.content.text: "Conversation"}</button>))}{hasMore ? (<button onClick={() => void loadMore()} type="button">Load more</button>) : null}</div>);}

4. Start a conversation

TypeScriptnew-conversation.tsx
import type { FormEvent } from "react";import { useCreateConversation } from "@getuserfeedback/chat-react";export function NewConversation({onCreated,}: {onCreated: (conversationId: string) => void;}) {const newConversation = useCreateConversation();async function submitMessage(event: FormEvent) {event.preventDefault();const trimmed = newConversation.text.trim();if (!trimmed) return;const result = await newConversation.create({ text: trimmed });onCreated(result.conversation.id);}return (<form onSubmit={submitMessage}><inputonChange={(event) => newConversation.setText(event.target.value)}value={newConversation.text}/><button disabled={newConversation.status === "pending"} type="submit">Send</button>{newConversation.error ? <p>Message not sent.</p> : null}</form>);}

5. Open a conversation and send a message

TypeScriptconversation-thread.tsx
import type { FormEvent } from "react";import { useState } from "react";import { useConversation } from "@getuserfeedback/chat-react";export function ConversationThread({conversationId,}: {conversationId: string;}) {const [text, setText] = useState("");const conversation = useConversation({ conversationId });async function submitMessage(event: FormEvent) {event.preventDefault();const trimmed = text.trim();if (!trimmed) return;setText("");await conversation.send({text: trimmed,});}if (conversation.status === "loading") {return <p>Loading messages...</p>;}return (<section>{conversation.hasMore ? (<button onClick={() => void conversation.loadMore()} type="button">Load older messages</button>) : null}<ol>{conversation.messages.map((message) => (<li key={message.id}><strong>{message.author}</strong>{" "}{message.content.type === "text" ? message.content.text : "Message"}</li>))}</ol><form onSubmit={submitMessage}><inputonChange={(event) => setText(event.target.value)}value={text}/><buttondisabled={conversation.sendState.status === "pending"}type="submit">Send</button></form></section>);}

Current limits

  • Realtime updates are not supported yet. Call refresh() after an app action where you expect new messages.
  • Sending currently supports plain text only.

Advanced: proxy through your backend

By default, the SDK calls https://api.getuserfeedback.com/v1 directly from your app. You can route requests through your own backend instead.

Pass an absolute baseUrl; the SDK uses that URL exactly, including any path prefix:

<GetUserFeedbackChatProviderclientOptions={{baseUrl: "https://app.example.com/api/chat/v1",auth: {jwt: {token: appSessionToken,},},}}><AppRoutes /></GetUserFeedbackChatProvider>

Your backend should authenticate that app session token, then call getuserfeedback.com with a JWT minted for your getuserfeedback.com app and that user.

Next