@traiyani/chatsdk-react
v1.1.0
Published
ChatSDK React SDK - Pre-built, plug-and-play real-time chat components for React
Maintainers
Readme
ChatSDK React SDK
React SDK for ChatSDK - Real-time chat solution with product-based conversations, Socket.IO, instant messaging, and file sharing. Fully isolated and self-contained.
Features
- Zero boilerplate — 3 steps: wrap with provider, login, use
<ChatPanel /> - Fully isolated — all dependencies bundled, no global side effects, scoped CSS
- React 16.8+ (hooks)
- Real-time messaging via Socket.IO
- Product-based conversations with metadata
- File/image/video uploads and sharing
- Block/unblock users
- Internationalization (English + Arabic with RTL)
- Dark mode support (light / dark / auto)
- Typing indicators, read receipts, unread counts
- Built-in unread badge & incoming-message toast — works out of the box, no extra code needed
- CSS auto-injection — no manual stylesheet import required
Table of Contents
- Installation
- SDK Initialization (Provider)
- User Authentication
- ChatPanel Component
- ChatPanel Props Reference
- Ref Methods
- Theme Support
- Internationalization (i18n)
- TypeScript Support
- Troubleshooting
1. Installation
npm install @traiyani/chatsdk-reactThat's it. axios and socket.io-client are bundled inside the SDK — your website does not need to install them.
| Dependency | Minimum Version |
|---|---|
| react | >= 16.8.0 |
| react-dom | >= 16.8.0 |
Works with Vite, webpack, Next.js, Create React App, Parcel, and any React bundler out of the box.
CSS (Auto-Injected)
CSS is automatically injected when you import any SDK export. No manual stylesheet import is needed.
import { ChatPanel } from '@traiyani/chatsdk-react';All SDK styles are scoped under .chatsdk-root so they never conflict with your app's CSS (Bootstrap, Tailwind, etc.).
If you prefer to import the stylesheet manually (e.g., for SSR or custom loading order):
import '@traiyani/chatsdk-react/dist/chatsdk.css';Optional: Load Fonts
The SDK UI uses Mulish and Lato fonts. If your app doesn't already load them, add this to your HTML <head>:
<link href="https://fonts.googleapis.com/css2?family=Mulish:wght@400;500;600;700&family=Lato:wght@400;700&display=swap" rel="stylesheet">2. SDK Initialization
Wrap your app (or the chat-using subtree) with <ChatSDKProvider>. This initializes the singleton SDK instance and makes it available to all child components.
// index.tsx or App.tsx
import { ChatSDKProvider } from '@traiyani/chatsdk-react';
const sdkConfig = {
apiBaseUrl: 'https://your-chat-api.com/api',
appId: 'your-app-id',
environment: 'production' as const,
};
function Root() {
return (
<ChatSDKProvider config={sdkConfig}>
<App />
</ChatSDKProvider>
);
}Configuration Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| apiBaseUrl | string | Yes | — | Chat API server URL |
| appId | string | Yes | — | Your application ID |
| environment | 'development' \| 'staging' \| 'production' | Yes | — | Environment |
| enableLogging | boolean | No | false | Enable debug logging in console |
| autoConnect | boolean | No | true | Auto-connect WebSocket after auth |
| timeout | number | No | 30000 | HTTP request timeout in ms |
| enablePluginUnreadAndIncomingToast | boolean | No | true | Enables the built-in unread badge and incoming-message toast. Set to false only if you want to build your own notification UI (see Advanced section). |
Environment Variables
Config values can come from any source:
// Create React App
const sdkConfig = {
apiBaseUrl: process.env.REACT_APP_API_URL,
appId: process.env.REACT_APP_APP_ID,
environment: process.env.REACT_APP_ENV,
};
// Vite
const sdkConfig = {
apiBaseUrl: import.meta.env.VITE_API_URL,
appId: import.meta.env.VITE_APP_ID,
environment: import.meta.env.VITE_ENVIRONMENT,
};
// Next.js
const sdkConfig = {
apiBaseUrl: process.env.NEXT_PUBLIC_API_URL,
appId: process.env.NEXT_PUBLIC_APP_ID,
environment: process.env.NEXT_PUBLIC_ENV,
};3. User Authentication
Use the useChatSDK() hook to login, verify users, and logout. These are the only auth calls your website needs to make.
3.1 Login the Current User
Call login() once after the website user authenticates (e.g., after website login). This saves the chat token and connects the WebSocket.
import { useChatSDK } from '@traiyani/chatsdk-react';
function AfterLogin() {
const { login } = useChatSDK();
const handleWebsiteLogin = async (websiteUser) => {
// ... your website login logic ...
// Then login to chat:
const chatUser = await login({
id: websiteUser.id, // Your app's user ID
name: websiteUser.name, // Display name
email: websiteUser.email, // Email
});
console.log('Chat user logged in:', chatUser.name);
};
return <button onClick={() => handleWebsiteLogin(myUser)}>Login</button>;
}What happens internally:
- Authenticates the user with the chat server (login or auto-register)
- Saves the auth token to localStorage
- Connects the WebSocket for real-time messaging
3.2 Verify Another User
Call verifyUser() to check if another user (e.g., a seller) exists in the chat system, or register them if they don't. This does NOT save a token or connect a socket — it's purely a lookup.
const { verifyUser } = useChatSDK();
const otherUser = await verifyUser({
id: 'seller-456',
name: 'Mohammed',
email: '[email protected]',
});
console.log('Verified:', otherUser.name, otherUser.id);3.3 Logout
Call logout() when the website user logs out. This disconnects the socket, clears the token, and cleans up all state.
const { logout } = useChatSDK();
await logout();3.4 Reactive State
The hook exposes shared state across all components:
const { currentUser, isInitialized, isConnected } = useChatSDK();
// In JSX:
// {currentUser?.name}
// {isConnected ? 'Online' : 'Offline'}4. ChatPanel Component
<ChatPanel /> is the main UI component. It handles everything internally: conversation layout, socket rooms, real-time events, language switching, and error containment.
4.1 Open a Direct Chat (mode = "chat")
Opens a chat popup with a specific user, optionally about a product.
import { useState } from 'react';
import { ChatPanel } from '@traiyani/chatsdk-react';
function ProductPage() {
const [showChat, setShowChat] = useState(false);
const product = {
id: 'CAR_123',
ownerId: 'seller-456',
name: 'Toyota Camry 2023',
image: 'https://example.com/car.jpg',
price: 25000,
category: 'Vehicles',
};
return (
<div>
<h1>{product.name}</h1>
<button onClick={() => setShowChat(true)}>Chat with Seller</button>
<ChatPanel
visible={showChat}
onVisibleChange={setShowChat}
mode="chat"
targetUser={{ id: product.ownerId }}
product={{
productId: product.id,
productName: product.name,
productImage: product.image,
price: product.price,
currency: 'QAR',
category: product.category,
}}
chatMetadata={{ source: 'product-page', adType: 'featured' }}
locale="en"
/>
</div>
);
}4.2 Open Conversation List (mode = "conversations")
Opens a panel with the conversation list sidebar and chat window.
import { useState, useRef } from 'react';
import { ChatPanel } from '@traiyani/chatsdk-react';
import type { ChatPanelRef } from '@traiyani/chatsdk-react';
function Navbar() {
const [showMessages, setShowMessages] = useState(false);
const [unread, setUnread] = useState(0);
const chatPanelRef = useRef<ChatPanelRef>(null);
return (
<nav>
<button onClick={() => setShowMessages(true)}>
Messages
{unread > 0 && <span className="badge">{unread}</span>}
</button>
<ChatPanel
ref={chatPanelRef}
visible={showMessages}
onVisibleChange={setShowMessages}
mode="conversations"
locale="en"
onUnreadCountChange={(count) => setUnread(count)}
/>
</nav>
);
}4.3 Chat with a User by Name + Email (No ID)
When you don't have the other user's external ID, pass their name and email. The SDK will authenticate/register them automatically.
<ChatPanel
visible={showChat}
onVisibleChange={setShowChat}
mode="chat"
targetUser={{ name: 'Ahmed', email: '[email protected]' }}
product={{ productId: '456', productName: 'Phone', productImage: 'https://...' }}
chatMetadata={{ source: 'user-profile' }}
locale="en"
/>4.4 Target User Resolution Logic
| What you pass | SDK behavior |
|---|---|
| { id: 'ext-123' } | Uses ID directly, skips user verification |
| { id: 'ext-123', name: 'Ahmed', email: '...' } | Uses ID directly, skips verification |
| { name: 'Ahmed', email: '[email protected]' } | Authenticates/registers user by name + email, then uses returned ID |
| { email: '[email protected]', phone: '+974...' } | Authenticates by email, phone stored as metadata |
Rule: If targetUser.id is present, the SDK skips verification. Otherwise, it authenticates the user first.
4.5 ExternalGroupId
The externalGroupId is a unique identifier for a conversation between two users (optionally about a product). It prevents duplicate conversations.
- If you provide
externalGroupId— the SDK uses it as-is. - If you omit it — the SDK generates one automatically using SHA-256 hash of the sorted user IDs + product ID.
{/* SDK generates groupId automatically */}
<ChatPanel
visible={showChat}
onVisibleChange={setShowChat}
mode="chat"
targetUser={{ id: 'seller-456' }}
product={{ productId: 'CAR_123', productName: 'Car', productImage: '...' }}
/>
{/* You provide your own groupId */}
<ChatPanel
visible={showChat}
onVisibleChange={setShowChat}
mode="chat"
targetUser={{ id: 'seller-456' }}
product={{ productId: 'CAR_123', productName: 'Car', productImage: '...' }}
externalGroupId={myCustomGroupId}
/>4.6 Complete Integration Example
Full working example — from install to running chat:
index.tsx:
import { ChatSDKProvider } from '@traiyani/chatsdk-react';
const sdkConfig = {
apiBaseUrl: process.env.REACT_APP_API_URL,
appId: process.env.REACT_APP_APP_ID,
environment: 'production' as const,
};
root.render(
<ChatSDKProvider config={sdkConfig}>
<App />
</ChatSDKProvider>
);LoginPage.tsx:
import { useChatSDK } from '@traiyani/chatsdk-react';
function LoginPage({ onLogin }) {
const { login } = useChatSDK();
const handleWebsiteLogin = async (websiteUser) => {
// ... your website login logic ...
await login({
id: websiteUser.id,
name: websiteUser.name,
email: websiteUser.email,
});
onLogin();
};
return <button onClick={() => handleWebsiteLogin(myUser)}>Login</button>;
}ProductPage.tsx:
import { useState } from 'react';
import { ChatPanel } from '@traiyani/chatsdk-react';
function ProductPage({ product }) {
const [showChat, setShowChat] = useState(false);
return (
<div>
<h1>{product.name}</h1>
<p>{product.price} QAR</p>
<button onClick={() => setShowChat(true)}>Chat with Seller</button>
<ChatPanel
visible={showChat}
onVisibleChange={setShowChat}
mode="chat"
targetUser={{ id: product.ownerId }}
product={{
productId: product.id,
productName: product.name,
productImage: product.image,
price: product.price,
currency: 'QAR',
}}
chatMetadata={{ source: 'product-page' }}
locale="en"
onError={(err) => console.error('Chat error:', err.message)}
/>
</div>
);
}Navbar.tsx (conversation list + unread badge):
import { useState, useRef } from 'react';
import { ChatPanel, useChatSDK } from '@traiyani/chatsdk-react';
import type { ChatPanelRef } from '@traiyani/chatsdk-react';
function Navbar() {
const { logout } = useChatSDK();
const [showMessages, setShowMessages] = useState(false);
const [unread, setUnread] = useState(0);
const handleLogout = async () => {
await logout();
// ... your website logout logic ...
};
return (
<nav>
<button onClick={() => setShowMessages(true)}>
Messages
{unread > 0 && <span className="badge">{unread}</span>}
</button>
<ChatPanel
visible={showMessages}
onVisibleChange={setShowMessages}
mode="conversations"
locale="en"
onUnreadCountChange={(count) => setUnread(count)}
/>
<button onClick={handleLogout}>Logout</button>
</nav>
);
}5. ChatPanel Props Reference
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| visible | boolean | Yes | — | Controls panel visibility. |
| onVisibleChange | (visible: boolean) => void | Yes | — | Called when panel wants to close. Use with visible for two-way binding. |
| mode | 'chat' \| 'conversations' | No | 'conversations' | 'chat' opens a direct chat popup. 'conversations' opens the conversation list with chat window. |
| targetUser | TargetUser | For mode="chat" | — | The other party to chat with. See Target User Resolution. |
| product | ProductContext | No | — | Product metadata to attach to the conversation. |
| chatMetadata | Record<string, any> | No | — | Additional metadata for the chat (e.g., source, ad type). |
| externalGroupId | string | No | — | Custom group ID. If omitted, the SDK generates one via SHA-256. |
| locale | 'en' \| 'ar' | No | — | Language. Reactive — changing it switches the language automatically. |
| onError | (error: Error) => void | No | — | Callback for errors. The panel catches all internal errors and calls this. |
| onUnreadCountChange | (count: number) => void | No | — | Callback when unread conversation count changes (polled every 30s). |
TargetUser
interface TargetUser {
id?: string; // External user ID — if present, skip verification
name?: string; // Display name
email?: string; // Email
phone?: string; // Phone number (stored as metadata)
}ProductContext
interface ProductContext {
productId: string;
productName: string;
productImage?: string;
price?: number;
currency?: string;
category?: string;
productMetadata?: Record<string, any>;
}6. Ref Methods
Access methods on the <ChatPanel /> via a React ref:
import { useRef } from 'react';
import { ChatPanel } from '@traiyani/chatsdk-react';
import type { ChatPanelRef } from '@traiyani/chatsdk-react';
function MyComponent() {
const chatPanelRef = useRef<ChatPanelRef>(null);
// Logout from chat (SDK + socket + clear tokens)
const handleLogout = async () => {
await chatPanelRef.current?.logout();
};
// Get current unread conversations count
const checkUnread = async () => {
const count = await chatPanelRef.current?.getUnreadCount();
console.log('Unread:', count);
};
return <ChatPanel ref={chatPanelRef} visible={showChat} onVisibleChange={setShowChat} />;
}| Method | Returns | Description |
|---|---|---|
| logout() | Promise<void> | Full cleanup: SDK logout + socket disconnect + clear tokens + close panel |
| getUnreadCount() | Promise<number> | Returns the number of conversations with unread messages |
| openConversationById(conversationId) | Promise<void> | Opens the panel and loads that conversation. Also used internally when the built-in incoming-message toast is clicked. |
Unread Badge & Incoming-Message Toast
Once you wrap your app with <ChatSDKProvider> and call login(), everything below works automatically — no extra code needed.
What you get for free:
Unread badge — a red pill showing how many conversations have unread messages. Drop it anywhere:
import { ChatSDKUnreadBadge } from '@traiyani/chatsdk-react'; <button>Messages <ChatSDKUnreadBadge /></button>Incoming-message toast — a dark bottom-right popup appears whenever someone sends you a new message. Clicking it opens that conversation.
Live count — the unread count updates in real time over the WebSocket. No polling needed.
Requirements:
- You must have a
<ChatPanel mode="conversations" visible={...} onVisibleChange={...} />mounted somewhere (e.g. your navbar). When the user clicks the toast, this panel opens the correct conversation. - The user must be logged in (
useChatSDK().login()) before counts appear.
Reading the count in script:
import { getChatSDKUnreadConversationsCount, subscribeChatSDKUnreadCount } from '@traiyani/chatsdk-react';
const count = getChatSDKUnreadConversationsCount(); // numberTurning it off: If you want to handle unread counts and notifications yourself, disable the built-in behavior:
<ChatSDKProvider config={{ ...config, enablePluginUnreadAndIncomingToast: false }}>Then see the Advanced section below.
Advanced: useUnreadConversationsCount
You can skip this section. The built-in badge and toast described above work without any of the code below. Only read on if you need one of these two things.
Scenario A — Run your own code when a message arrives
Use case: You want to send analytics, show a browser notification, or update something in your app whenever a new message comes in — but you still want the SDK's built-in toast to appear.
How to do it: Call the hook in a component that stays mounted (e.g. App.tsx):
import { useUnreadConversationsCount } from '@traiyani/chatsdk-react';
import type { IncomingMessagePreview } from '@traiyani/chatsdk-react';
function App() {
useUnreadConversationsCount({
incomingMessageMode: 'callback',
onIncomingMessage: (preview: IncomingMessagePreview) => {
// This runs every time someone sends you a message.
// The built-in toast still appears — this is extra.
console.log('New message in', preview.conversationId);
console.log('Text:', preview.preview);
},
});
return <>{/* ... */}</>;
}Nothing else to configure. The SDK toast still works; your callback runs alongside it.
Scenario B — Build your own notification UI (replace the SDK toast)
Use case: You don't want the SDK's built-in toast. Instead you want to show your own notification banner, popup, or any custom UI when a message arrives.
Step 1. Disable the built-in toast:
<ChatSDKProvider config={{ ...config, enablePluginUnreadAndIncomingToast: false }}>Step 2. Use the hook to get reactive data you can render:
import { useUnreadConversationsCount } from '@traiyani/chatsdk-react';
function App() {
const {
unreadConversationsCount, // number — how many conversations have unread messages
hasNewIncomingMessage, // boolean — true when a new message just arrived
lastIncomingMessage, // the message preview (sender, text, conversation id…)
clearNewIncomingMessage, // call this to dismiss your notification
} = useUnreadConversationsCount({
incomingMessageMode: 'refs',
realtime: true,
});
return (
<>
{/* Your custom notification */}
{hasNewIncomingMessage && (
<div className="my-notification">
{lastIncomingMessage?.senderName}: {lastIncomingMessage?.preview}
<button onClick={clearNewIncomingMessage}>Dismiss</button>
</div>
)}
<button>Messages ({unreadConversationsCount})</button>
</>
);
}Options & returns at a glance
| Option | What it does |
|---|---|
| incomingMessageMode: 'builtin' | Shows the SDK's built-in incoming-message toast. If the provider is active this is already enabled automatically. |
| incomingMessageMode: 'callback' | Calls your onIncomingMessage callback on each new message. Does not show the SDK toast on its own — but if the provider is active, the toast still appears alongside your callback. |
| incomingMessageMode: 'refs' | Fills hasNewIncomingMessage / lastIncomingMessage so you can build custom UI. No toast. |
| incomingMessageMode: 'none' | No incoming-message handling from this call. |
| realtime: true (default) | Keeps the unread count in sync over WebSocket. |
| realtime: false | Disables live updates for this instance. |
| Return value | Type | Description |
|---|---|---|
| unreadConversationsCount | number | Conversations with unread messages |
| hasNewIncomingMessage | boolean | true when a new preview is available |
| lastIncomingMessage | IncomingMessagePreview \| null | Sender name, text snippet, conversation id |
| clearNewIncomingMessage() | () => void | Resets hasNewIncomingMessage to false |
| refresh() | () => Promise<void> | Re-fetches the unread count from the server |
Note:
notifyOnIncomingMessage: trueis deprecated. UseincomingMessageMode: 'refs'instead.The user must be logged in (
useChatSDK().login()) before counts appear.
7. Theme Support
import { useTheme } from '@traiyani/chatsdk-react';
function ThemeSwitch() {
const { theme, actualTheme, setTheme, toggleTheme } = useTheme();
return (
<div>
<p>Current: {actualTheme}</p>
<button onClick={toggleTheme}>Toggle</button>
<button onClick={() => setTheme('dark')}>Dark</button>
<button onClick={() => setTheme('light')}>Light</button>
<button onClick={() => setTheme('auto')}>Auto (system)</button>
</div>
);
}8. Internationalization (i18n)
Supported languages: English (en), Arabic (ar with RTL).
The <ChatPanel /> accepts a reactive locale prop and handles language switching internally. No manual calls needed.
<ChatPanel
visible={showChat}
onVisibleChange={setShowChat}
mode="conversations"
locale={currentLocale}
/>If you need direct access to i18n functions:
import { useI18n } from '@traiyani/chatsdk-react';
function MyComponent() {
const { t, changeLanguage, getCurrentLanguage, isRTL } = useI18n();
return (
<div>
<h1>{t('conversations_title')}</h1>
<button onClick={() => changeLanguage('ar')}>Switch to Arabic</button>
</div>
);
}9. TypeScript Support
Full type declarations are included. No extra @types/* packages needed.
import type {
ChatSDKConfig,
ChatSDKUser,
ChatSDKMessage,
ChatSDKConversation,
ChatSDKLoginUser,
ProductContext,
TargetUser,
Product,
ChatState,
ChatPanelProps,
ChatPanelRef,
IncomingMessagePreview,
IncomingMessageMode,
UseUnreadConversationsCountOptions,
} from '@traiyani/chatsdk-react';ChatSDKConfig includes optional enablePluginUnreadAndIncomingToast. Exported components include ChatPanel, ChatSDKUnreadBadge, and IncomingMessageToast.
10. Troubleshooting
| Problem | Solution |
|---------|----------|
| "SDK not initialized" | Make sure <ChatSDKProvider> wraps your app in index.tsx before any SDK usage |
| "No logged-in user" | Call useChatSDK().login() after your website login, before opening ChatPanel |
| WebSocket not connecting | Check that apiBaseUrl is correct and reachable. Verify CORS allows your domain. |
| CSS not applying | CSS is auto-injected on import. If styles are missing, delete node_modules and reinstall. |
| Authentication fails | Double-check apiBaseUrl and appId match your chat server. |
| Panel not showing | Make sure visible={true} is passed and onVisibleChange updates the state. |
| Toast click does nothing | You need a <ChatPanel mode="conversations" visible={...} onVisibleChange={...} /> mounted somewhere. The toast opens conversations through that panel. |
| Toast shows wrong chat | Update to the latest SDK version. This was fixed in a recent release. |
| Unread badge shows 0 / toast never appears | Confirm login() completed successfully and enablePluginUnreadAndIncomingToast is not set to false. Check browser console for socket errors. |
License
MIT
