| 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
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
const room = await client.joinOrCreate('room_name', { options: 'value' });
const room = await client.create('room_name', { options: 'value' });
const room = await client.join('room_name', { options: 'value' });
const room = await client.joinById('room_id_here', { options: 'value' });
const room = await client.reconnect(reconnectionToken);
const room = await client.consumeSeatReservation(reservation);
React Integration
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
room.state
room.sessionId
room.id
room.name
room.send('message_type', { data: 'value' });
room.sendBytes(0, [0x01, 0x02, 0x03]);
room.leave();
room.leave(false);
room.removeAllListeners();
Room Events
room.onMessage('message_type', (data) => {
console.log('Received:', data);
});
room.onStateChange((state) => {
console.log('State updated:', state.toJSON());
});
room.onStateChange.once((state) => {
console.log('Initial state:', state);
});
room.onLeave((code) => {
console.log('Left room with code:', code);
});
room.onError((code, message) => {
console.error('Room error:', code, message);
});
HTTP Requests
client.http.get('/api/endpoint').then(response => {
console.log(response.data);
});
client.http.post('/api/endpoint', { body: 'data' }).then(response => {
console.log(response.data);
});
client.http.put('/api/endpoint', { body: 'data' }).then(response => {
console.log(response.data);
});
client.http.delete('/api/endpoint').then(response => {
console.log(response.data);
});
React Context Provider Pattern
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
client.auth.signInAnonymously()
.then((response) => {
console.log('Authenticated:', response.user);
})
.catch((error) => {
console.error('Auth error:', error);
});
client.auth.onChange((authData) => {
if (authData.token) {
console.log('User logged in:', authData.user);
} else {
console.log('User logged out');
}
});
client.http.get('/profile').then(response => {
});
Best Practices
- Leave room on unmount - Prevent memory leaks
- Handle errors - Network failures are common
- Use reconnection tokens - For seamless reconnection
- Send inputs, not positions - Server-authoritative
- Handle state changes - Use schema callbacks for updates
Connection Lifecycle Management
CRITICAL: Handle all connection states properly for a good UX.
enum ConnectionState {
DISCONNECTED = 'disconnected',
CONNECTING = 'connecting',
CONNECTED = 'connected',
RECONNECTING = 'reconnecting',
ERROR = 'error',
}
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);
joinedRoom.onLeave((code) => {
if (code === 1000) {
setState(ConnectionState.DISCONNECTED);
} else {
setState(ConnectionState.RECONNECTING);
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:
test('handles disconnect and reconnect', async ({ page }) => {
await page.goto('/game');
await expect(page.getByTestId('connection-status')).toHaveText('Connected');
await page.evaluate(() => {
window.__testDisconnectServer();
});
await expect(page.getByTestId('connection-status')).toHaveText('Disconnected');
await expect(page.getByTestId('connection-status')).toHaveText('Reconnecting', { timeout: 5000 });
await expect(page.getByTestId('connection-status')).toHaveText('Connected', { timeout: 10000 });
});
Connection State Monitoring
useEffect(() => {
const handleOnline = () => {
console.log('Browser online - attempt reconnect');
};
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