@opiniom/react
v0.6.0
Published
React chat SDK — provider, hooks, and drop-in widget for adding real-time messaging to React apps
Downloads
50
Maintainers
Readme
@opiniom/react
React bindings for the OpinioM chat SDK. Drop-in provider, pre-built widget, and 25+ hooks for building custom chat experiences.
Install
npm install @opiniom/react @opiniom/core @opiniom/typesPeer dependencies: react >= 18.0.0, react-dom >= 18.0.0
Quick Start
1. Sign a JWT on your backend
Your server signs a JWT with your OpinioM API secret key to identify the end-user:
// Node.js backend
import jwt from "jsonwebtoken";
const token = jwt.sign(
{ sub: user.id, name: user.name, email: user.email },
process.env.OPINIOM_API_SECRET
);2. Wrap your app with the provider
import { OpinioMProvider, ChatWidget } from "@opiniom/react";
function App() {
return (
<OpinioMProvider
projectKey="pk_live_abc123"
jwt={userToken}
onReady={(client) => console.log("Chat ready!", client)}
onError={(err) => console.error("Chat error:", err)}
>
<YourApp />
<ChatWidget />
</OpinioMProvider>
);
}That's it. A floating chat widget appears in the corner of your app.
Provider Props
<OpinioMProvider
projectKey="pk_live_abc123" // Required - your public project key
jwt={token} // Customer-signed JWT
getToken={() => fetchToken()} // Or: async function for token refresh
apiUrl="https://api.example.com" // Custom API URL
locale="en" // UI language (default: browser language)
theme={{ bubbleBgSelf: "#6366f1" }} // Theme overrides
debug={false} // Verbose logging
cache={false} // IndexedDB offline cache
onReady={(client) => {}} // Called when client is connected
onError={(err) => {}} // Called on unhandled errors
>
{children}
</OpinioMProvider>Components
ChatWidget
Pre-built chat widget with conversation list, message thread, composer, and reactions.
import { ChatWidget } from "@opiniom/react";
// Floating bubble (default)
<ChatWidget />
// Inline embed
<ChatWidget mode="inline" />InlineChat
Embedded inline chat component.
import { InlineChat } from "@opiniom/react";
<InlineChat />Hooks
Read Hooks (reactive state)
These hooks subscribe to real-time state and re-render your component when data changes.
import {
useChatClient,
useCurrentUser,
useConnectionStatus,
useConversations,
useConversation,
useMessages,
useTyping,
usePresence,
useReactions,
useUnreadCount,
useUnreadByConversation,
} from "@opiniom/react";
function MyChat() {
const client = useChatClient(); // OpinioMClient | null
const user = useCurrentUser(); // User | null
const status = useConnectionStatus(); // ClientState
const conversations = useConversations(); // Conversation[]
const conversation = useConversation(id); // Conversation | undefined
const { messages, loadMore, hasMore, isLoading } = useMessages(conversationId);
const typingUserIds = useTyping(conversationId); // string[]
const presence = usePresence(userId); // PresenceState | undefined
const reactions = useReactions(messageId); // { [emoji]: string[] }
const unreadTotal = useUnreadCount(); // number
const unreadMap = useUnreadByConversation(); // { [convId]: number }
}Action Hooks (imperative)
All action hooks return { isLoading, error, reset } alongside the action function.
import {
useSendMessage,
useEditMessage,
useDeleteMessage,
useCreateConversation,
useJoinConversation,
useLeaveConversation,
useUploadFile,
useAddReaction,
useRemoveReaction,
useSetTyping,
useMarkRead,
useSendMessageWithAttachments,
} from "@opiniom/react";
function MessageComposer({ conversationId }) {
const { send, isLoading, error } = useSendMessage(conversationId);
const { onKeystroke } = useSetTyping(conversationId);
const { markRead } = useMarkRead(conversationId);
const handleSend = async (text) => {
const message = await send({ content: text });
console.log("Sent:", message.id);
};
return (
<input
onChange={onKeystroke}
onKeyDown={(e) => e.key === "Enter" && handleSend(e.target.value)}
disabled={isLoading}
/>
);
}Send Message with Attachments
const { send, isUploading, isSending } = useSendMessageWithAttachments(conversationId);
// Uploads files first, then sends the message with attachment metadata
await send("Check out this file!", [selectedFile]);Create a Conversation
const { create } = useCreateConversation();
const conversation = await create({
type: "direct",
member_ids: ["user-123"],
});Building a Custom UI
You don't have to use <ChatWidget />. Use the hooks to build your own chat UI:
import {
OpinioMProvider,
useConversations,
useMessages,
useSendMessage,
useCurrentUser,
} from "@opiniom/react";
function CustomChat() {
const user = useCurrentUser();
const conversations = useConversations();
const [activeId, setActiveId] = useState(conversations[0]?.id);
const { messages, loadMore, hasMore } = useMessages(activeId);
const { send } = useSendMessage(activeId);
return (
<div className="chat">
<aside>
{conversations.map((c) => (
<button key={c.id} onClick={() => setActiveId(c.id)}>
{c.title}
</button>
))}
</aside>
<main>
{hasMore && <button onClick={loadMore}>Load more</button>}
{messages.map((m) => (
<div key={m.id}>
<strong>{m.sender.name}</strong>: {m.content}
</div>
))}
<input onKeyDown={(e) => {
if (e.key === "Enter") send({ content: e.target.value });
}} />
</main>
</div>
);
}
function App() {
return (
<OpinioMProvider projectKey="pk_live_abc123" jwt={token}>
<CustomChat />
</OpinioMProvider>
);
}TypeScript
All hooks and components are fully typed. Import types from @opiniom/types:
import type { Message, Conversation, User } from "@opiniom/types";Related Packages
| Package | Description | |---|---| | @opiniom/core | Framework-agnostic core client | | @opiniom/web | Lit-based web component widget | | @opiniom/types | TypeScript type definitions |
License
MIT
