React (JavaScript/TypeScript SDK)
Colyseus aims to provide a tighter integration with React in the future. If you’d like to contribute to this effort, please check this thread on Discord.
The objective is to iterate on @pedr0fontoura’s approach from his use-colyseus
project.
Raw / Simple Usage
The simplest way to use Colyseus in React is by using the useEffect
hook to join a room and handle its events. Make sure to leave the room when the component is unmounted.
import { useEffect } from "react";
import { Client, Room } from "colyseus.js";
const client = new Client("http://localhost:2567");
function RoomComponent () {
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);
// handle room events here
room.onStateChange((state) => setPlayers(state.players.toJSON()));
});
return () => {
// make sure to leave the room when the component is unmounted
req.then((room) => room.leave());
};
}, []);
return (
<div>
{players.map((player) => (
<div key={player.id}>{player.name}</div>
))}
</div>
);
}
Using a Context Provider for Room Management
TODO: Add a more complex example using a React Context Provider.
Alternatively, you can use a React Context Provider to manage the connection and room state across your application.
import React, { createContext, useContext } from 'react';
import { Room } from 'colyseus.js';
interface RoomContextType {
isConnecting: boolean;
isConnected: boolean;
room: Room;
join: () => void;
joinError: boolean;
state: any; // replace `any` with your state type
}
export const RoomContext = createContext<RoomContextType>({});
export function useRoom() { return useContext(RoomContext); }
let room!: Room;
//
// Workaround for React.StrictMode, to avoid multiple join requests
//
let hasActiveJoinRequest: boolean = false;
export function RoomProvider({ children }: { children: React.ReactNode }) {
const [searchParams, _] = useSearchParams();
const [joinError, setJoinError] = React.useState(false);
const [isConnecting, setIsConnecting] = React.useState(false);
const [isConnected, setIsConnected] = React.useState(false);
const [state, setState] = React.useState<ReturnType<BalootiState['toJSON']>>(undefined)
const join = () => {
if (hasActiveJoinRequest) { return; }
hasActiveJoinRequest = true;
setIsConnecting(true);
try {
room = await client.joinOrCreate("my_room");
} catch (e) {
setJoinError(true);
setIsConnecting(false);
return;
} finally {
hasActiveJoinRequest = false;
}
//
// cache reconnection token, if user goes back to this URL, we can try re-connect to the room.
// TODO: do not cache reconnection token if user is spectating
//
localStorage.setItem("reconnection", JSON.stringify({
token: room.reconnectionToken,
roomId: room.roomId,
}));
room.onStateChange((state) => setState(state.toJSON()));
room.onLeave(() => setIsConnected(false));
setIsConnected(true);
};
return (
<RoomContext.Provider value={{ isConnecting, isConnected, room, join, joinError, state }}>
{children}
</RoomContext.Provider>
);
}
Using a Context Provider for Authentication
You can also use a React Context Provider to manage the authentication state across your application. This is useful if you want to handle user authentication and authorization in a centralized way.
The following example shows how to create an AuthContext
that provides the authentication state and automatically signs in the user anonymously if no token is available.
You can customize it to meet your specific authentication needs.
import { createContext, useContext, useState, useEffect } from 'react';
import { Client } from "colyseus.js";
interface AuthContextType {
user: any;
loading: boolean;
}
const AuthContext = createContext<AuthContextType>(undefined);
export function AuthProvider({ colyseusSDK, children }: { colyseusSDK: Client, children: React.ReactNode }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const setUserData = (userData: any) => {
setUser(userData);
setLoading(false);
};
// Handle authentication on mount or token change
useEffect(() => {
colyseusSDK.auth.onChange((authData) =>
setUserData((authData.token) ? authData.user : null));
if (!colyseusSDK.auth.token) {
colyseusSDK.auth.signInAnonymously()
.then((response) => console.log("Anonymous login success:", response))
.catch((error) => console.error("Anonymous login error:", error));
}
}, [colyseusSDK]);
return (
<AuthContext.Provider value={{ user, loading, }}>
{children}
</AuthContext.Provider>
);
};
export function useAuth () {
return useContext(AuthContext);
};