Triviaverse / src /hooks /useMatchmaking.ts
Simeon Garratt
feat: multiplayer — matchmaking, 1v1 match with bot demo, match results with ELO
7edc107
Raw
History Blame Contribute Delete
1.93 kB
"use client";
import { useState, useCallback, useRef, useEffect } from "react";
import {
MatchmakingManager,
type MatchFoundData,
} from "@/lib/game/matchmaking";
/** The current phase of the matchmaking flow. */
export type MatchmakingState = "idle" | "searching" | "found" | "error";
/**
* React hook that wraps `MatchmakingManager` with component-friendly state.
*
* Returns reactive `state`, `playerCount`, `matchData`, and `error` values
* alongside `startSearching` / `cancelSearch` actions.
*
* Automatically cleans up the matchmaking channel on unmount.
*/
export function useMatchmaking(
playerId: string,
playerName: string,
playerElo: number,
) {
const [state, setState] = useState<MatchmakingState>("idle");
const [playerCount, setPlayerCount] = useState(0);
const [matchData, setMatchData] = useState<MatchFoundData | null>(null);
const [error, setError] = useState<string | null>(null);
const managerRef = useRef<MatchmakingManager | null>(null);
const startSearching = useCallback(async () => {
setState("searching");
setError(null);
const manager = new MatchmakingManager(playerId, playerName, playerElo);
managerRef.current = manager;
await manager.joinQueue({
onPlayerCount: setPlayerCount,
onMatchFound: (data) => {
setState("found");
setMatchData(data);
},
onError: (err) => {
setState("error");
setError(err);
},
});
}, [playerId, playerName, playerElo]);
const cancelSearch = useCallback(async () => {
if (managerRef.current) {
await managerRef.current.leaveQueue();
managerRef.current = null;
}
setState("idle");
setMatchData(null);
}, []);
// Cleanup on unmount
useEffect(() => {
return () => {
managerRef.current?.leaveQueue();
};
}, []);
return { state, playerCount, matchData, error, startSearching, cancelSearch };
}