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

@colyseus/react

v0.1.14

Published

React hooks for [Colyseus](https://colyseus.io/) multiplayer applications.

Readme

@colyseus/react

React hooks for Colyseus multiplayer applications.

Installation

npm install @colyseus/react

Peer dependencies: @colyseus/sdk, @colyseus/schema, and react (>=18.3.1).

Hooks

useRoom(callback, deps?)

Manages the lifecycle of a Colyseus room connection. Handles connecting, disconnecting on unmount, and reconnecting when dependencies change. Works correctly with React StrictMode.

import { Client } from "@colyseus/sdk";
import { useRoom } from "@colyseus/react";

const client = new Client("ws://localhost:2567");

function Game() {
  const { room, error, isConnecting } = useRoom(
    () => client.joinOrCreate("game_room"),
  );

  if (isConnecting) return <p>Connecting...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return <GameView room={room} />;
}

The first argument is a callback that returns a Promise<Room> — any Colyseus matchmaking method works (joinOrCreate, join, create, joinById, consumeSeatReservation).

Reconnecting on dependency changes:

const { room } = useRoom(
  () => client.joinOrCreate("game_room", { level }),
  [level],
);

When level changes the previous room is left and a new connection is established.

Conditional connection:

Pass a falsy value to skip connecting until a condition is met:

const { room } = useRoom(
  isReady ? () => client.joinOrCreate("game_room") : null,
  [isReady],
);

useRoomState(room, selector?)

Subscribes to Colyseus room state changes and returns immutable plain-object snapshots. Unchanged portions of the state tree keep referential equality between renders, so React components only re-render when the data they use actually changes.

import { useRoom, useRoomState } from "@colyseus/react";

function Game() {
  const { room } = useRoom(() => client.joinOrCreate("game_room"));
  const state = useRoomState(room);

  if (!state) return <p>Waiting for state...</p>;

  return <p>Players: {state.players.size}</p>;
}

Using a selector to subscribe to a subset of the state:

const players = useRoomState(room, (state) => state.players);

Only components that read players will re-render when the players map changes.

useRoomMessage(room, type, callback)

Subscribes to Colyseus room messages. The callback is kept in a ref so it is always up-to-date without re-subscribing. Automatically unsubscribes when the room changes or the component unmounts.

import { useRoom, useRoomMessage } from "@colyseus/react";

function Chat() {
  const { room } = useRoom(() => client.joinOrCreate("game_room"));
  const [messages, setMessages] = useState<string[]>([]);

  useRoomMessage(room, "chat", (message) => {
    setMessages((prev) => [...prev, message]);
  });

  return (
    <ul>
      {messages.map((msg, i) => <li key={i}>{msg}</li>)}
    </ul>
  );
}

Pass "*" as the type to listen to all message types.

useLobbyRoom(callback, deps?)

Connects to a Colyseus Lobby Room and provides a live-updating list of available rooms. The list is automatically maintained as rooms are created, updated, and removed.

import { Client } from "@colyseus/sdk";
import { useLobbyRoom } from "@colyseus/react";

const client = new Client("ws://localhost:2567");

function Lobby() {
  const { rooms, error, isConnecting } = useLobbyRoom(
    () => client.joinOrCreate("lobby"),
  );

  if (isConnecting) return <p>Connecting...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {rooms.map((room) => (
        <li key={room.roomId}>
          {room.name} — {room.clients}/{room.maxClients} players
        </li>
      ))}
    </ul>
  );
}

Return value:

| Field | Type | Description | |---|---|---| | rooms | RoomAvailable<Metadata>[] | Live list of available rooms | | room | Room \| undefined | The underlying lobby room connection | | error | Error \| undefined | Connection error, if any | | isConnecting | boolean | true while connecting to the lobby |

useQueueRoom(connect, consume, deps?)

Manages the full lifecycle of a Colyseus matchmaking queue: connecting to the queue room, tracking group size, receiving a seat reservation, confirming, and consuming the seat to join the match room. Cleans up both rooms on unmount.

import { Client } from "@colyseus/sdk";
import { useQueueRoom } from "@colyseus/react";

const client = new Client("ws://localhost:2567");

function Matchmaking() {
  const { room, clients, isWaiting, error } = useQueueRoom(
    () => client.joinOrCreate("queue", { rank: 1200 }),
    (reservation) => client.consumeSeatReservation(reservation),
  );

  if (error) return <p>Error: {error.message}</p>;
  if (room) return <GameScreen room={room} />;
  if (isWaiting) return <p>Waiting for match... {clients} players in group</p>;
  return <p>Connecting...</p>;
}

The first argument connects to the queue room. The second argument is called with the SeatReservation once a match is found — use client.consumeSeatReservation() to join the match room.

Return value:

| Field | Type | Description | |---|---|---| | room | Room \| undefined | The match room, once the seat has been consumed | | queue | Room \| undefined | The queue room while waiting (undefined after match is joined) | | clients | number | Number of clients in the current matchmaking group | | seat | SeatReservation \| undefined | The seat reservation, once received | | error | Error \| undefined | Connection or matchmaking error | | isWaiting | boolean | true while connected to the queue and waiting for a match |

Contexts

createRoomContext()

Creates a set of hooks and a RoomProvider component that share a single room connection across React reconciler boundaries (e.g. DOM + React Three Fiber). The room is stored in a closure-scoped external store rather than React Context, so the hooks work in any reconciler tree that imports them.

import { Client } from "@colyseus/sdk";
import { createRoomContext } from "@colyseus/react";

const client = new Client("ws://localhost:2567");

const { RoomProvider, useRoom, useRoomState } = createRoomContext();

Wrap your app with RoomProvider:

function App() {
  return (
    <RoomProvider connect={() => client.joinOrCreate("game_room")}>
      <UI />
      <Canvas>
        <GameScene />
      </Canvas>
    </RoomProvider>
  );
}

RoomProvider accepts a connect callback (same as the standalone useRoom hook) and an optional deps array. Pass a falsy value to connect to defer the connection.

Use the hooks in any component — DOM or R3F:

function UI() {
  const { room, error, isConnecting } = useRoom();
  const players = useRoomState((state) => state.players);

  if (isConnecting) return <p>Connecting...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return <p>Players: {players?.size}</p>;
}

The returned useRoom() and useRoomState(selector?) work identically to the standalone hooks but don't require you to pass the room as an argument.

createLobbyContext()

Creates a LobbyProvider and useLobby hook for sharing lobby room data globally across your app — useful when you need room metadata available persistently alongside an active game room, not just on a lobby screen. Like createRoomContext, it uses a closure-scoped external store so the hook works across reconciler boundaries.

import { Client } from "@colyseus/sdk";
import { createLobbyContext, createRoomContext } from "@colyseus/react";

const client = new Client("ws://localhost:2567");

const { LobbyProvider, useLobby } = createLobbyContext<MyMetadata>();
const { RoomProvider, useRoom, useRoomState } = createRoomContext();

Wrap your app with LobbyProvider (can nest with RoomProvider):

function App() {
  return (
    <LobbyProvider connect={() => client.joinOrCreate("lobby")}>
      <RoomProvider connect={() => client.joinOrCreate("game_room")}>
        <UI />
        <Canvas>
          <GameScene />
        </Canvas>
      </RoomProvider>
    </LobbyProvider>
  );
}

LobbyProvider accepts a connect callback (same as useLobbyRoom) and an optional deps array. The lobby connection persists independently of the game room.

Access lobby data from any component — even deep inside the game:

function RoomBrowser() {
  const { rooms, error, isConnecting } = useLobby();

  if (isConnecting) return <p>Loading rooms...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {rooms.map((room) => (
        <li key={room.roomId}>
          {room.metadata.displayName} — {room.clients}/{room.maxClients}
        </li>
      ))}
    </ul>
  );
}

The returned useLobby() hook provides the same fields as useLobbyRoom (rooms, room, error, isConnecting).

Credits

Inspiration and previous work by @pedr0fontourause-colyseus.