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

@yappr/react

v0.1.0

Published

A thin React binding for Yappr real-time chat. Wrap your tree in `<YapprProvider>`, then read any channel with one hook:

Readme

@yappr/react

A thin React binding for Yappr real-time chat. Wrap your tree in <YapprProvider>, then read any channel with one hook:

const { messages, status, unreadCount, hasOlder, send, loadOlder, markRead } =
  useChannel("room1");

Requirements

  • React 18 or 19 (react/react-dom are peer deps: ^18.0.0 || ^19.0.0).
  • A reachable Yappr server (ws:// or wss://) supplied by your operator.
  • Client-rendered — components that call useChannel or render <YapprProvider> must run in the browser. See SSR.

Install

Once published (the normal case — nothing special):

pnpm add @yappr/react @yappr/core

Before publish (local tarball testing). @yappr/react's manifest depends on @yappr/core by version, so installing the react tarball alone makes pnpm look @yappr/core up on the npm registry and fail with ERR_PNPM_FETCH_404. Drop both tarballs into your project root, add a pnpm override pinning @yappr/core to the local file, then install:

// package.json
{
  "dependencies": {
    "@yappr/core": "file:./yappr-core-0.1.0.tgz",
    "@yappr/react": "file:./yappr-react-0.1.0.tgz"
  },
  "pnpm": { "overrides": { "@yappr/core": "file:./yappr-core-0.1.0.tgz" } }
}
pnpm install

(The override is only needed for local tarballs; once the packages are on npm it is unnecessary.)


Configuration (you must provide these)

createClient needs four values that come from your Yappr server operator — an AI agent must ASK for these and must not invent them:

| value | what it is | local dev example | |------------|---------------------------------------------|-----------------------| | url | the server origin (ws:// or wss://) | ws://localhost:8787 | | tenantId | your tenant id | t_dev | | key | your publishable key | pk_test_localdev | | channel id | which room to join | room1 |


Minimal Next.js (App Router) integration

Copy this into a "use client" component. Substitute the url/tenantId/key your operator gave you and the app's signed-in userId.

// app/chat/chat-client.tsx
"use client";
import { useState } from "react";
import { createClient, YapprProvider, useChannel } from "@yappr/react";

// Created once at module scope → a stable reference (never inline in render).
const client = createClient({
  url: "ws://localhost:8787", // ← from your operator
  tenantId: "t_dev",          // ← from your operator
  key: "pk_test_localdev",    // ← from your operator
  userId: "alice",            // the signed-in user; any non-empty string when testing without auth
  displayName: "Alice",       // optional
});

export function Chat({ channelId = "room1" }: { channelId?: string }) {
  return (
    <YapprProvider client={client}>
      <Room channelId={channelId} />
    </YapprProvider>
  );
}

function Room({ channelId }: { channelId: string }) {
  const { messages, status, send } = useChannel(channelId);
  const [draft, setDraft] = useState("");
  return (
    <div>
      <p>status: {status}</p>
      <ul>
        {messages.map((m) => (
          <li key={m.id}>
            <b>{m.senderId}:</b> {m.content}
            {m.status !== "sent" && <em> ({m.status})</em>}
          </li>
        ))}
      </ul>
      <form onSubmit={(e) => { e.preventDefault(); if (draft.trim()) { send(draft); setDraft(""); } }}>
        <input value={draft} onChange={(e) => setDraft(e.target.value)} />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}

A Server Component page just renders <Chat />. Everything touching useChannel or <YapprProvider> must live under a "use client" boundary.

@yappr/react and @yappr/core ship compiled JS + types, so no bundler tweaks are needed — you do not need transpilePackages in next.config.


API reference

createClient(config): YapprClient

Re-exported from @yappr/core. Creates a WebSocket client. Call it once — see Gotchas.

| config field | type | required | description | |----------------|----------|----------|---------------------------------------| | url | string | yes | WebSocket server origin | | tenantId | string | yes | your tenant id | | key | string | yes | your publishable key | | userId | string | yes | identity of the signed-in user | | displayName | string | no | display name shown to other users |

<YapprProvider client={client}>

Puts a YapprClient on React context. Stateless — does not open or close the connection.

useChannel(channelId: string): UseChannelResult

Subscribes the component to a channel. Returns:

| field | type | notes | |---------------|---------------------------------------------------------|-------------------------------------------------------------------| | messages | Message[] | ascending; includes your own optimistic sends | | status | "connecting" \| "connected" \| "disconnected" | live connection state | | unreadCount | number | messages since your last read position | | hasOlder | boolean | more history available to page in | | send | (content: string) => void | optimistic; buffered + retried on reconnect if offline. A server rejection yields status: "failed" (see Gotchas) | | loadOlder | () => Promise<void> | pages older history; resolves when done | | markRead | (seq: number) => void | advances read position (clears unread badge) |

send, loadOlder, and markRead are stable across renders.

Message shape

type Message =
  | (Envelope & { status: "sent" })      // confirmed by server — has a `seq` field
  | { status: "sending" | "failed";      // optimistic / not yet confirmed — no `seq`
      id: string; senderId: string; content: string; createdAt: number; kind: "user" }

seq only exists on confirmed ("sent") messages. Narrow on status before reading it:

const newestSeq = messages.reduce(
  (max, m) => (m.status === "sent" && m.seq > max ? m.seq : max),
  0,
);
markRead(newestSeq);

Gotchas

Stable client — create once. useChannel never closes channels; the client owns connection lifetime. If you write <YapprProvider client={createClient(...)}> inline in render, a new socket is opened every render and the old one is orphaned. Create the client at module scope (or in a useEffect with a cleanup that calls client.close()).

"use client" is required. In Next.js App Router, any component that calls useChannel or renders <YapprProvider> must be inside a "use client" boundary. Server Components cannot use these.

send is optimistic. It returns immediately and the message appears in messages with status: "sending". It flips to status: "sent" when the server confirms, or status: "failed" if rejected. Rendering a retry UI for "failed" messages is app-level responsibility — @yappr/react surfaces the status but does not render UI for it.

Failed sends are not retried automatically. A "failed" message stays in the list. If you want retry, call send(m.content) again when the user triggers it.


Key safety

The key value (pk_test_localdev in local dev) is a publishable key. Whether it is safe to ship in a browser bundle is a production and operator decision. The local pk_test_localdev is for local development only and should never be used in production.


SSR

useChannel provides a stable getServerSnapshot so server rendering or prerendering does not crash or hydration-mismatch. On the server (and the first hydration render) the snapshot is { status: "connecting", messages: [], unreadCount: 0, hasOlder: false }; the WebSocket is opened only in the browser, and no messages are fetched on the server. In Next.js App Router, mark any component using this package with "use client".


For AI agents

  1. Ask the human for url, tenantId, and key (see Configuration). Do not invent them.
  2. Install the package(s) (see Install).
  3. Create a "use client" component using the snippet above; substitute the provided url/tenantId/key and the app's signed-in userId.
  4. Render it from a page. In Next.js App Router, the component (or its wrapper) must be "use client".
  5. Verify: the status shows connected and a sent message appears and turns sent.

Contributor note (monorepo developers)

This package is part of the yappr monorepo. For local development and a runnable demo, see apps/web. From the repo root:

pnpm --filter @yappr/server-app dev:setup   # once: migrate + seed tenant t_dev / key pk_test_localdev
pnpm --filter @yappr/server-app dev          # Yappr server on :8787
pnpm --filter @yappr/web dev                 # demo app (open the printed URL)

The demo's ChatPane/ChannelView are a working reference implementation for the patterns in this README.