Skip to main content

React SDK

@cstar.help/react wraps the JS client in idiomatic React hooks. Loading, error, and refetch states are handled for you. Two provider trees — one for authenticated chat, one for the public knowledge base — so customer-facing surfaces and admin surfaces stay separate.

Installation

npm install @cstar.help/react

Pulls in @cstar.help/js as a transitive dep. Needs React 18 or newer. Real-time messaging is on by default — no extra package to install.

Browse by topic

Drilling into something specific? Topics with a dedicated sub-page are linked below; the rest scroll to the matching section on this page.

Setup

Chat hooks

Library hooks

Recipes

Two providers, two surfaces

Chat needs authentication and runs over real-time. Library is public, no auth, fine to render statically. They live in separate provider trees so you can mount one, the other, or both — without one bleeding state into the other.

CStarChatProvider

Wrap your app (or chat widget) with the chat provider to enable customer tickets and messages. Only teamSlug is required — real-time delivery is enabled by default with an automatic polling fallback.

app/providers.tsx
import { CStarChatProvider } from '@cstar.help/react';

export default function Providers({ children }) {
  return (
    <CStarChatProvider teamSlug="acme">
      {children}
    </CStarChatProvider>
  );
}

Pass realtime=false to disable the live connection and poll instead (every 3 seconds by default). Real-time is recommended for production.

Chat Hooks

useChat

Identify customers and manage the chat session. Must call identify() with an HMAC signature before using other chat hooks.

import { useChat } from '@cstar.help/react';

function ChatWidget() {
  const {
    identify,        // (customer, hmacSignature) => Promise<IdentifyResult>
    disconnect,      // () => void
    isIdentified,    // boolean
    isRealtimeReady, // boolean
    error            // Error | null
  } = useChat();

  async function startChat() {
    await identify({
      externalId: 'user_123',
      email: 'jane@example.com',
      name: 'Jane Doe',
      timestamp: Math.floor(Date.now() / 1000)
    }, 'hmac_signature_from_your_server');
  }

  if (!isIdentified) {
    return <button onClick={startChat}>Start Chat</button>;
  }

  return <div>Chat ready! {isRealtimeReady ? '(live)' : '(polling)'}</div>;
}

useTickets

List and create the customer's tickets.

import { useTickets } from '@cstar.help/react';

function TicketList() {
  const {
    tickets,   // Ticket[]
    isLoading, // boolean
    error,     // Error | null
    hasMore,   // boolean
    refresh,   // () => Promise<void>
    create     // (params) => Promise<Ticket>
  } = useTickets();

  async function startNew() {
    const ticket = await create({
      title: 'Help with billing',
      message: 'I have a question about my invoice.'
    });
    console.log('Created:', ticket.id);
  }

  if (isLoading) return <p>Loading...</p>;

  return (
    <div>
      {tickets.map(t => (
        <div key={t.id}>
          <h3>{t.title}</h3>
          <span>{t.status} · {t.messageCount} messages</span>
        </div>
      ))}
      <button onClick={startNew}>New Ticket</button>
    </div>
  );
}

useMessages

Send and receive messages on a ticket. Includes optimistic updates and real-time delivery.

import { useMessages } from '@cstar.help/react';

function MessageThread({ ticketId }) {
  const {
    messages,  // Message[]
    isLoading, // boolean
    error,     // Error | null
    send,      // (content: string) => Promise<Message>
    refresh    // () => Promise<void>
  } = useMessages(ticketId);

  const [draft, setDraft] = useState('');

  async function handleSend() {
    await send(draft); // Optimistic — appears instantly
    setDraft('');
  }

  return (
    <div>
      {messages.map(msg => (
        <div key={msg.id}>
          <strong>{msg.sender_name}</strong>
          <p>{msg.content}</p>
        </div>
      ))}
      <input value={draft} onChange={e => setDraft(e.target.value)} />
      <button onClick={handleSend}>Send</button>
    </div>
  );
}

useTyping

Show typing indicators for agents. Indicators auto-clear after 4 seconds of inactivity.

import { useTyping } from '@cstar.help/react';

function TypingIndicator({ ticketId }) {
  const {
    typingAgents, // { agentId, agentName }[]
    sendTyping    // (isTyping: boolean) => Promise<void>
  } = useTyping(ticketId);

  if (typingAgents.length === 0) return null;

  const names = typingAgents.map(a => a.agentName).join(', ');
  return <p>{names} is typing...</p>;
}

CStarLibraryProvider

Wrap your help center with the library provider. No authentication needed — the knowledge base is public.

app/help/layout.tsx
import { CStarLibraryProvider } from '@cstar.help/react';

export default function HelpLayout({ children }) {
  return (
    <CStarLibraryProvider teamSlug="acme">
      {children}
    </CStarLibraryProvider>
  );
}

Library Hooks

useCategories

import { useCategories } from '@cstar.help/react';

function CategoryList() {
  const { categories, isLoading, error, refresh } = useCategories();

  if (isLoading) return <p>Loading...</p>;

  return (
    <div>
      {categories.map(cat => (
        <a key={cat.id} href={`/help/${cat.slug}`}>
          {cat.name} ({cat.article_count} articles)
        </a>
      ))}
    </div>
  );
}

useArticles

import { useArticles } from '@cstar.help/react';

function ArticleList({ categorySlug }) {
  const { articles, isLoading } = useArticles({
    categorySlug,
    limit: 10
  });

  if (isLoading) return <p>Loading...</p>;

  return (
    <div>
      {articles.map(article => (
        <a key={article.id} href={`/help/articles/${article.slug}`}>
          <h3>{article.title}</h3>
          <p>{article.excerpt}</p>
        </a>
      ))}
    </div>
  );
}

useArticle

import { useArticle } from '@cstar.help/react';

function ArticlePage({ slug }) {
  const { article, isLoading, error } = useArticle(slug);

  if (isLoading) return <p>Loading...</p>;
  if (!article) return <p>Article not found</p>;

  return (
    <article>
      <h1>{article.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: article.content }} />
    </article>
  );
}

useArticleSearch

Full-text search with built-in 300ms debounce.

import { useArticleSearch } from '@cstar.help/react';

function SearchBar() {
  const [query, setQuery] = useState('');
  const { results, totalCount, isLoading } = useArticleSearch(query);

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search articles..."
      />
      {isLoading && <p>Searching...</p>}
      <p>{totalCount} results</p>
      {results.map(article => (
        <a key={article.id} href={`/help/articles/${article.slug}`}>
          {article.title}
        </a>
      ))}
    </div>
  );
}

Community Hooks

Wrap in <CStarCommunityProvider> for public community forum access — no auth required.

CStarCommunityProvider

import { CStarCommunityProvider } from '@cstar.help/react';

function App() {
  return (
    <CStarCommunityProvider teamSlug="acme">
      <CommunityForum />
    </CStarCommunityProvider>
  );
}

useTopics

import { useTopics } from '@cstar.help/react';

function TopicNav() {
  const { topics, isLoading } = useTopics();

  return (
    <nav>
      {topics.map(topic => (
        <a key={topic.id}>{topic.name} ({topic.slug})</a>
      ))}
    </nav>
  );
}

usePosts

import { usePosts } from '@cstar.help/react';

function PostList() {
  const { posts, count, isLoading } = usePosts({
    topicSlug: 'feature-requests',
    sort: 'votes'
  });

  return (
    <div>
      <p>{count} posts</p>
      {posts.map(post => (
        <div key={post.id}>
          <h3>{post.title}</h3>
          <span>{post.voteCount} votes · {post.commentCount} comments</span>
        </div>
      ))}
    </div>
  );
}

usePost

import { usePost } from '@cstar.help/react';

function PostPage({ slug }) {
  const { data, isLoading, error } = usePost(slug);

  if (!data) return null;

  return (
    <>
      <h1>{data.post.title}</h1>
      <p>{data.post.body}</p>
      {data.comments.map(c => (
        <div key={c.id}>
          <strong>{c.authorName}</strong>: {c.body}
        </div>
      ))}
    </>
  );
}

useCommunitySearch

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>
      ))}
    </>
  );
}