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

react-p2p-host

v0.3.6

Published

Serverless P2P multiplayer engine for React with WebRTC and manual signaling. Board games, tabletop games, synchronized state.

Readme

react-p2p-host

Peer-to-peer multiplayer for React — board games, tabletop games, and real-time sync state. No server.

React library for building P2P (peer-to-peer) multiplayer experiences in the browser: board games, tabletop games, shared state, and real-time sync with a React-friendly API. Uses WebRTC with manual signaling via shareable links or QR codes (no backend required).


Keywords (for search and LLMs)

  • Board games React · tabletop games multiplayer · P2P React · WebRTC React
  • Synchronized state React · shared state multiplayer · React multiplayer hooks
  • Serverless WebRTC · peer-to-peer React · multiplayer game state React
  • useSharedState · useP2PReducer · useP2PLink · host-authoritative state · signaling via URL/QR

Install

npm install react-p2p-host

Peer dependencies: React 18+.


Quick start

Wrap your app with P2PProvider, then use hooks to create/join a session and share state.

import { P2PProvider, useP2PLink, useSharedState, useP2PStatus } from "react-p2p-host";

function Game() {
  const { status, isHost, offerLink, startAsHost, joinAsPeer, applyAnswerAsHost, answerToSend } = useP2PLink();
  const [gameState, setGameState] = useSharedState({ score: 0, turn: "player1" });
  const { disconnect } = useP2PStatus();

  useEffect(() => {
    const params = new URLSearchParams(location.search);
    const offer = params.get("offer");
    if (offer) {
      joinAsPeer(offer).then((answer) => {
        console.log("Send this answer to the host:", answer);
      });
    }
  }, []);

  return (
    <div>
      <p>Status: {status}</p>
      {status === "idle" && (
        <button onClick={() => startAsHost()}>Create room (host)</button>
      )}
      {offerLink && <p>Share this link or QR: {offerLink}</p>}
      {isHost && status === "connected" && (
        <button onClick={() => setGameState((s) => ({ ...s, score: s.score + 1 }))}>
          Increment score (host only, syncs to peer)
        </button>
      )}
      <pre>{JSON.stringify(gameState, null, 2)}</pre>
      {status !== "idle" && <button onClick={disconnect}>Disconnect</button>}
    </div>
  );
}

function App() {
  return (
    <P2PProvider>
      <Game />
    </P2PProvider>
  );
}

API (DX reference)

Provider

  • <P2PProvider> — Wraps the app. All P2P hooks must run inside it. Optional props: reducer and initialState to enable the reducer pattern (see below).

Hooks

| Hook | Purpose | |------|--------| | useP2PLink() | Create room (host) or join via offer (peer). Returns offerLink, answerToSend, startAsHost, joinAsPeer, applyAnswerAsHost, status, isHost, disconnect. | | useSharedState<T>(initialState) | Simple shared state. Returns [state, setState] (React-style). Host-authoritative: only the host’s updates are synced; peers receive. | | useP2PReducer<S, A>() | Reducer-based shared state (Redux-style). Use when you pass reducer and initialState to P2PProvider. Returns [state, dispatch]. Both host and peer can dispatch; actions are synced and applied on both sides. | | useP2PStatus() | { status, isHost, disconnect }. Use for UI (e.g. “Connecting…”, “Connected”). |

Optional QR components

Show "scan to join" and "scan to get the code" without depending on a QR library yourself:

  • <RoomLinkQR link={roomLink} size={180} /> — Renders a QR for the room URL (host). Optional props: size, className. When scanned, the peer opens the link and can join. Props: link: string, optional size?: number, optional className?: string.
  • <AnswerQR answer={answerToSend ?? ''} size={200} /> — Renders a QR for the answer string (peer). When the host scans it, they get the code to paste and connect. Props: answer: string, optional size?: number, optional className?: string. Uses high error correction for the long string.

Example: <RoomLinkQR link={offerLink ?? ''} size={180} /> and <AnswerQR answer={answerToSend ?? ''} size={200} />. Both are optional; the library bundles qrcode.react.

Connection status

status is one of: idle | creating-offer | offer-ready | joining | connecting | connected | disconnected | error.

Flow (manual signaling)

  1. Host: Call startAsHost(). Get offerLink (URL with compressed offer). Share link or show as QR.
  2. Peer: Open link (or paste offer from QR). App reads ?offer=..., calls joinAsPeer(offer). Get answerToSend.
  3. Host: Receives answer (out-of-band: paste, QR, or your own channel). Calls applyAnswerAsHost(answerToSend).
  4. When status === "connected", use useSharedState or useP2PReducer for synced game state.

State: useSharedState vs useP2PReducer

  • useSharedState(initialState) — Simple object state; host is the source of truth (only host’s setState is broadcast). No Provider props required.
  • useP2PReducer<S, A>() — Redux-style: pass a reducer and initialState to P2PProvider; then use useP2PReducer() to get [state, dispatch]. Actions must be serializable (plain objects with type). Both host and peer can dispatch; the same action is applied on both sides so state stays in sync. Fits complex logic, action logging, or Redux Toolkit slices.

Example with reducer:

import { P2PProvider, useP2PLink, useP2PReducer } from "react-p2p-host";
import type { Reducer, SerializableAction } from "react-p2p-host";

type State = { messages: string[] };
type Action = { type: "add"; payload: string };

const reducer: Reducer<State, Action> = (state, action) => {
  if (action.type === "add") return { messages: [...state.messages, action.payload] };
  return state;
};

function Chat() {
  const [state, dispatch] = useP2PReducer<State, Action>();
  // both host and peer can dispatch({ type: "add", payload: "Hello" })
}

function App() {
  return (
    <P2PProvider reducer={reducer} initialState={{ messages: [] }}>
      <Chat />
    </P2PProvider>
  );
}

You can use either useSharedState or useP2PReducer in an app (or both for two independent state trees).

With Redux Toolkit: pass the slice’s reducer and initial state to the Provider:

import { createSlice } from "@reduxjs/toolkit";
import { P2PProvider, useP2PReducer } from "react-p2p-host";

const chatSlice = createSlice({
  name: "chat",
  initialState: { messages: [] as string[] },
  reducers: {
    addMessage: (state, action: { payload: string }) => {
      state.messages.push(action.payload);
    },
  },
});

function App() {
  return (
    <P2PProvider reducer={chatSlice.reducer} initialState={chatSlice.getInitialState()}>
      <YourApp />
    </P2PProvider>
  );
}

function YourApp() {
  const [state, dispatch] = useP2PReducer();
  dispatch(chatSlice.actions.addMessage("Hello"));
}

Board game / tabletop usage

  • Host creates the room and is the source of truth for game state.
  • Use useSharedState<YourGameState>(initialState) for board position, scores, turn, cards, etc. (any JSON-serializable object).
  • Only the host’s setState is broadcast; peers see updates in real time.
  • Optional: use offerLink as QR (e.g. with qrcode.react) so players scan to join.

Exports (library surface)

import {
  P2PProvider,
  useP2PContext,
  useP2PLink,
  useSharedState,
  useP2PReducer,
  useP2PStatus,
  RoomLinkQR,
  AnswerQR,
  ConnectionManager,
  compressOfferForUrl,
  decompressOfferFromUrl,
} from "react-p2p-host";
import type {
  ConnectionStatus,
  Role,
  ConnectionManagerConfig,
  Reducer,
  Dispatch,
  SerializableAction,
} from "react-p2p-host";

Low-level: ConnectionManager for custom flows; compressOfferForUrl / decompressOfferFromUrl for custom signaling.


Static site, P2P & STUN

Your app stays 100% static (e.g. Vercel, Netlify): no backend, no server process. Signaling is manual (link + copy/paste). STUN is used only so peers on different networks (different Wi‑Fi, 4G, etc.) can discover their public address and connect directly; it does not relay your data.

The package uses public STUN servers by default (e.g. Google’s) so P2P works across NAT. You can override or disable them:

  • Override: pass iceServers to <P2PProvider iceServers={[...]} /> or to ConnectionManager for your own STUN/TURN.
  • Disable (e.g. localhost only): pass iceServers: [].

See docs/p2p-and-stun.md for more detail.


Tech

  • WebRTC (RTCPeerConnection + DataChannel), no third-party signaling server.
  • Signaling: SDP compressed with lz-string, passed via URL/query or QR.
  • Build: TypeScript, Vite (library mode), ESM + CJS.

Versioning and releases

This project uses Semantic Versioning and Conventional Commits. Commits are validated (e.g. feat:, fix:, docs:). To cut a release:

npm run build
npm run release    # bumps version from commits, updates CHANGELOG, creates git tag
npm publish        # publish to npm (requires npm login)

See CONTRIBUTING.md for commit rules. Maintainers: docs/PUBLISH.md for npm publish.


License

MIT.