---
name: dev-multiplayer-colyseus-client
description: Colyseus client SDK for React, connection methods, room events, and messaging. Use when connecting to multiplayer server.
category: multiplayer
---

# Colyseus Client SDK

Connect to Colyseus server from React applications with colyseus.js.

## When to Use

Use when:
- Connecting to game server from client
- Handling room state changes
- Sending/receiving messages
- Managing room lifecycle

## Basic Connection

```typescript
import { Client, Room } from 'colyseus.js';

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

async function joinGame() {
  try {
    const room = await client.joinOrCreate('game_room', {});

    room.onStateChange((state) => {
      console.log('New state:', state);
    });

    room.onMessage('game_event', (data) => {
      console.log('Received:', data);
    });

    room.onLeave((code) => {
      console.log('Left room:', code);
    });
  } catch (e) {
    console.error('Join error:', e);
  }
}
```

## Connection Methods

```typescript
// Join existing or create new
const room = await client.joinOrCreate('room_name', { options: 'value' });

// Create new (fails if exists)
const room = await client.create('room_name', { options: 'value' });

// Join existing only (fails if full/not found)
const room = await client.join('room_name', { options: 'value' });

// Join by specific room ID
const room = await client.joinById('room_id_here', { options: 'value' });

// Reconnect to previous room
const room = await client.reconnect(reconnectionToken);

// Consume seat reservation
const room = await client.consumeSeatReservation(reservation);
```

## React Integration

```tsx
import { useEffect, useRef, useState } from 'react';
import { Client, Room } from 'colyseus.js';

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

function GameRoom() {
  const roomRef = useRef<Room>();
  const [isConnecting, setIsConnecting] = useState(true);
  const [players, setPlayers] = useState([]);

  useEffect(() => {
    const req = client.joinOrCreate('my_room', {});

    req.then((room) => {
      roomRef.current = room;
      setIsConnecting(false);

      room.onStateChange((state) => setPlayers(state.players.toJSON()));
    });

    return () => {
      req.then((room) => room.leave());
    };
  }, []);

  return (
    <div>
      {isConnecting ? 'Connecting...' :
        players.map((player) => (
          <div key={player.id}>{player.name}</div>
        ))
      }
    </div>
  );
}
```

## Room Methods

```typescript
// Properties
room.state        // Current room state (Schema instance)
room.sessionId    // Unique client identifier
room.id           // Room identifier (shareable)
room.name         // Room handler name

// Send message to server
room.send('message_type', { data: 'value' });

// Send raw bytes
room.sendBytes(0, [0x01, 0x02, 0x03]);

// Leave room
room.leave();              // Consented leave
room.leave(false);         // Forced leave

// Remove all listeners
room.removeAllListeners();
```

## Room Events

```typescript
// Message events
room.onMessage('message_type', (data) => {
  console.log('Received:', data);
});

// State change events
room.onStateChange((state) => {
  console.log('State updated:', state.toJSON());
});

// One-time state change (first state only)
room.onStateChange.once((state) => {
  console.log('Initial state:', state);
});

// Leave event
room.onLeave((code) => {
  console.log('Left room with code:', code);
  // 1000 = normal shutdown
  // 4000-4999 = custom codes
});

// Error event
room.onError((code, message) => {
  console.error('Room error:', code, message);
});
```

## HTTP Requests

```typescript
// GET request
client.http.get('/api/endpoint').then(response => {
  console.log(response.data);
});

// POST request
client.http.post('/api/endpoint', { body: 'data' }).then(response => {
  console.log(response.data);
});

// PUT request
client.http.put('/api/endpoint', { body: 'data' }).then(response => {
  console.log(response.data);
});

// DELETE request
client.http.delete('/api/endpoint').then(response => {
  console.log(response.data);
});
```

## React Context Provider Pattern

```tsx
// RoomContext.tsx
import React, { createContext, useContext, useState, useEffect } from 'react';
import { Client, Room } from 'colyseus.js';

interface RoomContextType {
  room: Room | null;
  state: any;
  isConnected: boolean;
  join: () => void;
}

export const RoomContext = createContext<RoomContextType>({} as RoomContextType);

export function useRoom() {
  return useContext(RoomContext);
}

export function RoomProvider({ children }: { children: React.ReactNode }) {
  const [room, setRoom] = useState<Room | null>(null);
  const [state, setState] = useState<any>(null);
  const [isConnected, setIsConnected] = useState(false);

  const join = async () => {
    try {
      const client = new Client('ws://localhost:2567');
      const joinedRoom = await client.joinOrCreate('game_room');

      setRoom(joinedRoom);
      setIsConnected(true);

      joinedRoom.onStateChange((newState) => {
        setState(newState.toJSON());
      });

      joinedRoom.onLeave(() => {
        setIsConnected(false);
        setRoom(null);
      });
    } catch (e) {
      console.error('Failed to join:', e);
    }
  };

  return (
    <RoomContext.Provider value={{ room, state, isConnected, join }}>
      {children}
    </RoomContext.Provider>
  );
}
```

## Authentication

```typescript
// Anonymous sign-in
client.auth.signInAnonymously()
  .then((response) => {
    console.log('Authenticated:', response.user);
  })
  .catch((error) => {
    console.error('Auth error:', error);
  });

// Listen to auth changes
client.auth.onChange((authData) => {
  if (authData.token) {
    console.log('User logged in:', authData.user);
  } else {
    console.log('User logged out');
  }
});

// Auth tokens sent automatically with HTTP requests
client.http.get('/profile').then(response => {
  // Authorization header included
});
```

## Best Practices

1. **Leave room on unmount** - Prevent memory leaks
2. **Handle errors** - Network failures are common
3. **Use reconnection tokens** - For seamless reconnection
4. **Send inputs, not positions** - Server-authoritative
5. **Handle state changes** - Use schema callbacks for updates

## Connection Lifecycle Management

**CRITICAL:** Handle all connection states properly for a good UX.

```typescript
// Connection states enum
enum ConnectionState {
  DISCONNECTED = 'disconnected',
  CONNECTING = 'connecting',
  CONNECTED = 'connected',
  RECONNECTING = 'reconnecting',
  ERROR = 'error',
}

// Connection hook with lifecycle
function useColyseusConnection(roomName: string) {
  const [state, setState] = useState<ConnectionState>(ConnectionState.DISCONNECTED);
  const [room, setRoom] = useState<Room | null>(null);
  const [error, setError] = useState<string | null>(null);

  const connect = async () => {
    setState(ConnectionState.CONNECTING);
    setError(null);

    try {
      const client = new Client('ws://localhost:2567');
      const joinedRoom = await client.joinOrCreate(roomName);

      setRoom(joinedRoom);
      setState(ConnectionState.CONNECTED);

      // Handle disconnection
      joinedRoom.onLeave((code) => {
        if (code === 1000) {
          setState(ConnectionState.DISCONNECTED);
        } else {
          setState(ConnectionState.RECONNECTING);
          // Attempt reconnection
          setTimeout(() => connect(), 3000);
        }
      });

      joinedRoom.onError((err) => {
        setError(err.message);
        setState(ConnectionState.ERROR);
      });

    } catch (err) {
      setError(err.message);
      setState(ConnectionState.ERROR);
    }
  };

  const disconnect = () => {
    if (room) {
      room.leave();
      setRoom(null);
      setState(ConnectionState.DISCONNECTED);
    }
  };

  return { state, error, connect, disconnect, room };
}
```

### Reconnection Testing Pattern

**For E2E tests covering disconnect/reconnect cycles:**

```typescript
// E2E test for reconnection
test('handles disconnect and reconnect', async ({ page }) => {
  // 1. Connect to server
  await page.goto('/game');
  await expect(page.getByTestId('connection-status')).toHaveText('Connected');

  // 2. Simulate server disconnect
  await page.evaluate(() => {
    // @ts-ignore - Test utility
    window.__testDisconnectServer();
  });

  // 3. Verify disconnected state
  await expect(page.getByTestId('connection-status')).toHaveText('Disconnected');

  // 4. Wait for auto-reconnect
  await expect(page.getByTestId('connection-status')).toHaveText('Reconnecting', { timeout: 5000 });

  // 5. Verify reconnected
  await expect(page.getByTestId('connection-status')).toHaveText('Connected', { timeout: 10000 });
});
```

### Connection State Monitoring

```typescript
// Browser's online/offline events
useEffect(() => {
  const handleOnline = () => {
    console.log('Browser online - attempt reconnect');
    // Trigger reconnection logic
  };

  const handleOffline = () => {
    console.log('Browser offline - show disconnected');
    setState(ConnectionState.DISCONNECTED);
  };

  window.addEventListener('online', handleOnline);
  window.addEventListener('offline', handleOffline);

  return () => {
    window.removeEventListener('online', handleOnline);
    window.removeEventListener('offline', handleOffline);
  };
}, []);
```

## Common Mistakes

| ❌ Wrong | ✅ Right |
|----------|----------|
| Not leaving room on unmount | Always `room.leave()` in cleanup |
| Using CommonJS imports | Use `import { Client } from 'colyseus.js'` |
| Client sends absolute position | Client sends input (WASD, aim) |
| Ignoring error events | Always handle `onError` |

## Reference

- [Colyseus Client Docs](https://docs.colyseus.io/client)
- [React Integration](https://docs.colyseus.io/getting-started/react)


---
Source attribution: feliperyba
Original project: https://github.com/feliperyba/ralph-orchestra/blob/7a74951367fe9bec093753f6eaa4d0eeda9428e5/.claude/skills/dev-multiplayer-colyseus-client/SKILL.md
License: MIT
License information: https://opensource.org/license/mit
Original upstream instructions; downloaded from Dave AI.

## Upstream license notice

MIT License

Copyright (c) 2026 feliperyba

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



# Dave AI resource metadata

Creator / publisher: feliperyba
Original project: https://github.com/feliperyba/ralph-orchestra/blob/7a74951367fe9bec093753f6eaa4d0eeda9428e5/.claude/skills/dev-multiplayer-colyseus-client/SKILL.md
Attribution basis: Recovered original package and original project license at a pinned revision

Fields of work: Development & Engineering
Roles: Frontend Developer, Full-Stack Developer, Game Developer
Specialties: Backend, Cross Platform, Frontend
Tools: Colyseus
Country applicability: Country not specified
Classification basis: Source catalog labels
Instruction SHA-256: 5b24ec28096cc3430d5967d55fbaf6ab84c8beb682f0379552b0d06838d25d2f
Metadata reviewed: 2026-10-27
This metadata supersedes earlier Dave AI catalog attribution. Original instruction and supporting files are preserved.
