React 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.

RoomComponent.tsx
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.

RoomContext.tsx
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.

AuthContext.tsx
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);
};
Last updated on