commit 43d574f447e1f9f22a947bf9116586cc2a494a6c
parent 6b31420aea44554db46dcdbe0ba01a3bbe349787
Author: Andreas Gruhler <agruhl@gmx.ch>
Date: Mon, 16 Mar 2026 17:56:55 +0100
feat(leaderboard): cleanup websocket on component change
This stores the websocket connection in the Leaderboard as a state
variable.
The variable is cleaned up when the component Leaderboard is exited. For
example, when a judge switches to a different tab/component (e.g.,
Scoring), the socket to the server is closed and only recreated when the
tab/component is changed again (to the Leaderboard).
Diffstat:
1 file changed, 31 insertions(+), 26 deletions(-)
diff --git a/src/frontend/Leaderboard.jsx b/src/frontend/Leaderboard.jsx
@@ -10,21 +10,6 @@ const ws_port = import.meta.env.VITE_WS_PORT
const locale = import.meta.env.VITE_LOCALE
-// use a socket for the real-time leaderboard data
-let socket = new WebSocket(`${ws_uri}:${ws_port}/v1/leaderboard`);
-console.info("Attached to server websocket");
-
-socket.onclose = async function(event) {
- console.info("Server removed us from client list, reattaching socket");
- socket = new WebSocket(`${ws_uri}:${ws_port}/v1/leaderboard`);
-}
-socket.onopen = function (event) {
- // subscribe to scoring from judges when socket is opened
- socket.send(JSON.stringify({
- method: "watchScores",
- }))
-}
-
export async function addAthleteToHeat(athlete, heat, session) {
const res = await fetch(`${api_uri}:${api_port}/v1/leaderboard/addAthleteToHeat`, {
method: 'POST',
@@ -257,6 +242,7 @@ function NewHeatForm(leaderboard, rankingComp, selectHeatRef, selectRankRef, ses
}
function Leaderboard({session}) {
+ const [socket, setSocket] = useState(null)
const [loading, setLoading] = useState(false)
const [leaderboard, setLeaderboard] = useState([])
const [heatSelection, setHeatSelection] = useState([])
@@ -318,6 +304,26 @@ function Leaderboard({session}) {
]
useEffect(() => {
+ let ws = new WebSocket(`${ws_uri}:${ws_port}/v1/leaderboard`);
+ console.info("Attached to server websocket");
+
+ ws.addEventListener("open", event => {
+ // subscribe to scoring from judges when socket is opened
+ ws.send(JSON.stringify({
+ method: "watchScores",
+ }))
+ });
+
+ setSocket(ws)
+
+ // cleanup websocket when component is changed
+ return () => {
+ console.info("Websocket connection closed");
+ ws.close();
+ }
+ }, []);
+
+ useEffect(() => {
(async () => {
setLoading(true)
@@ -348,18 +354,17 @@ function Leaderboard({session}) {
const scoreSummary = await getScoreSummary(heatSelection.map(h => h.value), session)
setLeaderboard(scoreSummary)
setLoading(false)
- })();
- }, [heatSelection]);
- useEffect(() => {
- (async() => {
- socket.onmessage = async function(event) {
- setLoading(true)
-
- // todo: reload only required scores
- const scoreSummary = await getScoreSummary(heatSelection.map(h => h.value), session)
- setLeaderboard(scoreSummary)
- setLoading(false)
+ // reload leaderboard on messages from the database (new scores from judges arrived)
+ if (socket !== null) {
+ socket.addEventListener("message", async (event) => {
+ setLoading(true)
+
+ // todo: reload only required scores
+ const scoreSummary = await getScoreSummary(heatSelection.map(h => h.value), session)
+ setLeaderboard(scoreSummary)
+ setLoading(false)
+ });
}
})();
}, [heatSelection]);