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

bash
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.

jsx
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.

jsx
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.

jsx
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.

jsx
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

jsx
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

jsx
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

jsx
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.

jsx
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

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

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

useTopics

tsx
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

tsx
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

tsx
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

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