@cstar.help/react
v0.6.1
Published
React hooks for the cStar customer support platform
Maintainers
Readme
@cstar.help/react
React hooks for the cStar customer support platform. Build custom chat widgets and knowledge base UIs with type-safe, SSR-compatible hooks.
- React 18+ — hooks with automatic loading states and error handling
- Three modules — Chat (real-time messaging), Library (knowledge base), and Community (forum)
- TypeScript-first — full type definitions included
- Tiny — ~18 KB, tree-shakeable, only peer deps
Install
npm install @cstar.help/react @cstar.help/jsChat
Wrap your app in <CStarChatProvider> and use hooks anywhere inside.
Setup
import { CStarChatProvider } from '@cstar.help/react';
function App() {
return (
<CStarChatProvider teamSlug="acme" supabaseUrl="..." supabaseAnonKey="...">
<ChatWidget />
</CStarChatProvider>
);
}Identify the Customer
import { useChat } from '@cstar.help/react';
function ChatWidget() {
const { identify, isIdentified, isRealtimeReady, error } = useChat();
useEffect(() => {
identify(
{ externalId: 'usr_123', email: '[email protected]', timestamp: Math.floor(Date.now() / 1000) },
hmacSignature // computed server-side
);
}, []);
if (!isIdentified) return <p>Connecting...</p>;
return <ConversationList />;
}Conversations
import { useConversations } from '@cstar.help/react';
function ConversationList() {
const { conversations, isLoading, create, refresh, hasMore } = useConversations();
if (isLoading) return <Spinner />;
return (
<ul>
{conversations.map((c) => (
<li key={c.id}>{c.subject}</li>
))}
</ul>
);
}Messages + Typing Indicators
import { useMessages, useTyping } from '@cstar.help/react';
function ChatWindow({ ticketId }) {
const { messages, isLoading, send } = useMessages(ticketId);
const { typingAgents, sendTyping } = useTyping(ticketId);
const [text, setText] = useState('');
const handleSend = async () => {
await send(text); // optimistic — appears instantly
setText('');
};
return (
<>
{messages.map((msg) => (
<div key={msg.id}>{msg.content}</div>
))}
{typingAgents.map((a) => (
<p key={a.agentId}>{a.agentName} is typing...</p>
))}
<input
value={text}
onChange={(e) => {
setText(e.target.value);
sendTyping(e.target.value.length > 0);
}}
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
/>
</>
);
}Knowledge Base
Wrap in <CStarLibraryProvider> for public knowledge base access — no auth required.
Setup
import { CStarLibraryProvider } from '@cstar.help/react';
function App() {
return (
<CStarLibraryProvider teamSlug="acme">
<HelpCenter />
</CStarLibraryProvider>
);
}Categories + Articles
import { useCategories, useArticles, useArticle } from '@cstar.help/react';
function HelpCenter() {
const { categories, isLoading: catsLoading } = useCategories();
const { articles, isLoading: artsLoading } = useArticles({ categorySlug: 'getting-started' });
return (
<>
<nav>
{categories.map((cat) => (
<a key={cat.id}>{cat.name}</a>
))}
</nav>
<ul>
{articles.map((a) => (
<li key={a.id}>{a.title}</li>
))}
</ul>
</>
);
}
// Single article by slug
function ArticlePage({ slug }) {
const { article, isLoading, error } = useArticle(slug);
if (isLoading) return <Spinner />;
if (!article) return <NotFound />;
return <h1>{article.title}</h1>;
}Search with Debouncing
import { useArticleSearch } from '@cstar.help/react';
function SearchBar() {
const [query, setQuery] = useState('');
const { results, totalCount, isLoading } = useArticleSearch(query); // 300ms debounce built-in
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search..." />
{isLoading && <Spinner />}
{results.map((a) => (
<a key={a.id} href={`/articles/${a.slug}`}>
{a.title}
</a>
))}
</>
);
}Community
Wrap in <CStarCommunityProvider> for public community forum access — no auth required.
Setup
import { CStarCommunityProvider } from '@cstar.help/react';
function App() {
return (
<CStarCommunityProvider teamSlug="acme">
<CommunityForum />
</CStarCommunityProvider>
);
}Topics + Posts
import { useTopics, usePosts, usePost } from '@cstar.help/react';
function CommunityForum() {
const { topics, isLoading: topicsLoading } = useTopics();
const { posts, count, isLoading: postsLoading } = usePosts({ sort: 'votes' });
return (
<>
<nav>
{topics.map((topic) => (
<a key={topic.id}>{topic.name}</a>
))}
</nav>
<p>{count} posts</p>
<ul>
{posts.map((post) => (
<li key={post.id}>
{post.title} — {post.voteCount} votes
</li>
))}
</ul>
</>
);
}
// Single post with comments
function PostPage({ slug }) {
const { data, isLoading, error } = usePost(slug);
if (isLoading) return <Spinner />;
if (!data) return <NotFound />;
return (
<>
<h1>{data.post.title}</h1>
{data.comments.map((c) => (
<p key={c.id}>{c.body}</p>
))}
</>
);
}Search
import { useCommunitySearch } from '@cstar.help/react';
function CommunitySearch() {
const { results, isLoading, search } = useCommunitySearch();
return (
<>
<input onChange={(e) => search(e.target.value)} placeholder="Search posts..." />
{results?.data.map((post) => (
<a key={post.id}>{post.title}</a>
))}
</>
);
}API Reference
Chat Hooks
| Hook | Returns | Description |
| ----------------------- | ---------------------------------------------------------------- | ---------------------------------------------------- |
| useChat() | { identify, disconnect, isIdentified, isRealtimeReady, error } | Identity verification and connection state |
| useConversations() | { conversations, isLoading, error, hasMore, refresh, create } | List and create conversations |
| useMessages(ticketId) | { messages, isLoading, error, send, refresh } | Messages with real-time updates and optimistic send |
| useTyping(ticketId) | { typingAgents, sendTyping } | Agent typing indicators with auto-clear (4s timeout) |
Library Hooks
| Hook | Returns | Description |
| ------------------------- | ------------------------------------------- | ----------------------------------------- |
| useCategories() | { categories, isLoading, error, refresh } | Knowledge base categories |
| useArticles(params?) | { articles, isLoading, error, refresh } | Articles, optionally filtered by category |
| useArticle(slug) | { article, isLoading, error, refresh } | Single article by slug |
| useArticleSearch(query) | { results, totalCount, isLoading, error } | Search with 300ms debounce |
Community Hooks
| Hook | Returns | Description |
| ---------------------- | --------------------------------------------- | ----------------------------------------------------- |
| useTopics() | { topics, isLoading, error, refresh } | Community discussion topics |
| usePosts(params?) | { posts, count, isLoading, error, refresh } | Posts with optional filters (topicSlug, status, sort) |
| usePost(slug) | { data, isLoading, error, refresh } | Single post with comments |
| useCommunitySearch() | { results, isLoading, error, search } | On-demand search across posts |
Context Accessors
| Function | Returns | Description |
| ---------------------- | ----------------- | ------------------------------------------------ |
| useChatClient() | ChatClient | Raw client from nearest CStarChatProvider |
| useLibraryClient() | LibraryClient | Raw client from nearest CStarLibraryProvider |
| useCommunityClient() | CommunityClient | Raw client from nearest CStarCommunityProvider |
Requirements
- React 18+
- @cstar.help/js 0.1.0+
- Node.js 18+ (for SSR)
License
MIT
