npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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

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/types

Peer 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