ccs-chat-user-client for React
The /react entry point of ccs-chat-user-client adds a context provider and data hooks on top of ChatUserClient. It needs React 18 or later.
Install
Section titled “Install”Install the package as described on the package page. react is an optional peer dependency, so your app already provides it. Import from the sub-path:
import { ChatUserClientProvider, useConversations, useMessages } from '@central-code-station/ccs-chat-user-client/react';Provide the client
Section titled “Provide the client”Create one ChatUserClient outside your components, or in useMemo, and pass it to ChatUserClientProvider. Hooks read it from context.
import { ChatUserClient } from '@central-code-station/ccs-chat-user-client';import { ChatUserClientProvider, useCapabilities, useConversations } from '@central-code-station/ccs-chat-user-client/react';import { context } from './fe-guide-context';
// Create the client once, outside components.const chat = new ChatUserClient(context);
export function App() { return ( <ChatUserClientProvider client={chat}> <Inbox onOpen={(conversationId) => console.log('open', conversationId)} /> </ChatUserClientProvider> );}
function Inbox({ onOpen }: { onOpen: (conversationId: string) => void }) { const { items, loading, hasMore, loadMore, error } = useConversations({ limit: 30 }); const { data: me } = useCapabilities(); const canStartChats = me?.capabilities.includes('conversations.create') ?? false;
if (error) return <p>Could not load your conversations.</p>; return ( <ul> {loading && <li>Loading...</li>} {canStartChats && <li><button>New chat</button></li>} {items.map((c) => ( <li key={c.conversation_id} onClick={() => onOpen(c.conversation_id)}> {c.name ?? c.type} {c.has_unread ? '(new)' : ''} </li> ))} {hasMore && <li><button onClick={() => void loadMore()}>Load more</button></li>} </ul> );}| Hook | Returns |
|---|---|
useChatUserClient() |
The client from the provider, for writes such as send and markRead. It throws outside the provider. |
useCapabilities() |
{ data, loading, error, reload, mutate } with the user’s Chat permissions. |
useConversations({ type?, limit? }) |
{ items, hasMore, loadMore, loading, loadingMore, error, reload, mutate }. |
useMessages(conversationId, { limit? }) |
The same, newest first. Nothing loads while conversationId is null or undefined. |
useChatRequest(load, deps, options?) |
Any one request, with { data, loading, error, reload, mutate }. options.enabled pauses it. |
useChatPages(loadPage, deps, options?) |
Any cursor-paged list, with the same result as useConversations. |
The hooks reload when their inputs change and drop a response that arrives after a newer request. mutate updates the loaded data locally without a request, which is how you insert a live message.
import { useChatUserClient, useMessages } from '@central-code-station/ccs-chat-user-client/react';import { useChatNotificationEvent } from '@central-code-station/ccs-chat-notifications-client/react';import { useLiveClient } from './fe-guide-react-live';
export function Thread({ conversationId }: { conversationId: string }) { const chat = useChatUserClient(); const live = useLiveClient(); const { items, hasMore, loadMore, mutate } = useMessages(conversationId); // newest first
useChatNotificationEvent(live, 'message.created', ({ conversation_id, message }) => { if (conversation_id !== conversationId) return; // The sender can get their own message back too, so skip ids you already have. mutate((messages) => (messages.some((m) => m.message_id === message.message_id) ? messages : [message, ...messages])); });
useChatNotificationEvent(live, 'message.deleted', ({ conversation_id, message_id }) => { if (conversation_id !== conversationId) return; mutate((messages) => messages.map((m) => (m.message_id === message_id ? { ...m, status: 'DELETED', body: '' } : m))); });
return ( <div> {hasMore && <button onClick={() => void loadMore()}>Load older</button>} {[...items].reverse().map((m) => ( <p key={m.message_id}>{m.status === 'DELETED' ? 'Message deleted' : m.body}</p> ))} <button onClick={() => void chat.messages.send(conversationId, { body: 'See you at 9!' })}>Send</button> </div> );}Any other call
Section titled “Any other call”useChatRequest(load, deps, options?) loads one request and reloads it when deps change. load needs no stable identity: the latest one is used. Pass enabled: false to pause it.
import { useChatRequest } from '@central-code-station/ccs-chat-user-client/react';
/** Any call can use the generic hook. It reloads when `deps` change and pauses while `enabled` is false. */export function ClientSearch({ query }: { query: string }) { const { data, loading, error } = useChatRequest((chat) => chat.users.lookup(query), [query], { enabled: query.length >= 2 });
if (error) return <p>Search is not available.</p>; return ( <ul> {loading && <li>Searching...</li>} {data?.map((user) => <li key={user.user_id}>{user.display_name}</li>)} </ul> );}useChatNotifications and useChatNotificationEvent come from ccs-chat-notifications-client for React.
What the adapter does not do
Section titled “What the adapter does not do”- The hooks are not a cache. They do not share results between components or keep them after unmount. If your app uses TanStack Query or SWR, call the client’s methods from those instead.
- It has no mutation hooks. Use
useChatUserClient()for writes and update the list withmutate. - It has no live updates. It loads data in effects, so nothing is fetched during a server render.
- It does not get the token for you. See Get the user’s Chat token.