Skip to content

Commit 08af55a

Browse files
tahminatorArshadul-Monir
authored andcommitted
490: Abstracted duel navigation into a hook that triggers side-effects
1 parent 6b3ab8f commit 08af55a

7 files changed

Lines changed: 76 additions & 168 deletions

File tree

js/src/app/duel/[lobbyCode]/_components/party/PartyWaitingBody.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,12 @@ export default function PartyWaitingBody({
4848
});
4949
},
5050
onSuccess: (data) => {
51-
notifications.show({
52-
message: data.message,
53-
color: data.success ? undefined : "red",
54-
});
51+
if (!data.success) {
52+
notifications.show({
53+
message: data.message,
54+
color: "red",
55+
});
56+
}
5557
},
5658
});
5759
};
@@ -64,10 +66,12 @@ export default function PartyWaitingBody({
6466
});
6567
},
6668
onSuccess: (data) => {
67-
notifications.show({
68-
message: data.message,
69-
color: data.success ? undefined : "red",
70-
});
69+
if (!data.success) {
70+
notifications.show({
71+
message: data.message,
72+
color: "red",
73+
});
74+
}
7175
},
7276
});
7377
};

js/src/app/duel/_components/DuelBody.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useLobbyNavigation } from "@/app/duel/_hooks/useDuelNavigation";
12
import PartyWaitingBody from "@/app/duel/[lobbyCode]/_components/party/PartyWaitingBody";
23
import ToastWithRedirect from "@/components/ui/toast/ToastWithRedirect";
34
import {
@@ -16,6 +17,7 @@ export function CurrentDuelBody({
1617
currentUser: Api<"UserDto">;
1718
}) {
1819
const query = useMyDuelOrPartyData();
20+
useLobbyNavigation();
1921

2022
return <DuelBody query={query} currentUser={currentUser} playable />;
2123
}
@@ -41,9 +43,9 @@ function DuelBody({
4143
query: ReturnType<typeof useDuelOrPartyData>;
4244
playable?: boolean;
4345
}) {
44-
const { data, status, error } = query;
46+
const { data } = query;
4547

46-
if (status === "pending") {
48+
if (!data) {
4749
return (
4850
<Flex
4951
direction={"column"}
@@ -57,15 +59,6 @@ function DuelBody({
5759
);
5860
}
5961

60-
if (status === "error" || error) {
61-
return (
62-
<ToastWithRedirect
63-
to={"/duel"}
64-
message={"Hmm, something went wrong."}
65-
/>
66-
);
67-
}
68-
6962
if (!data || !data.success) {
7063
return (
7164
<ToastWithRedirect

js/src/app/duel/_components/PartyCreate.tsx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import { useCreatePartyMutation } from "@/lib/api/queries/duels";
22
import { Box, Button } from "@mantine/core";
33
import { notifications } from "@mantine/notifications";
4-
import { useQueryClient } from "@tanstack/react-query";
5-
import { useNavigate } from "react-router";
64

75
export default function PartyCreate() {
86
const { mutate } = useCreatePartyMutation();
97

10-
const queryClient = useQueryClient();
11-
const navigate = useNavigate();
12-
138
const onCreate = () => {
149
mutate(undefined, {
1510
onSuccess: async (data) => {
@@ -20,9 +15,6 @@ export default function PartyCreate() {
2015
});
2116
return;
2217
}
23-
24-
await queryClient.invalidateQueries({ queryKey: ["party"] });
25-
navigate(`/duel/current`);
2618
},
2719
});
2820
};

js/src/app/duel/_components/PartyEntry.tsx

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,11 @@
1-
import ToastWithRedirect from "@/components/ui/toast/ToastWithRedirect";
2-
import { useGetCurrentDuelOrPartyQuery } from "@/lib/api/queries/duels";
3-
import { Flex, Divider, Loader, Card, Stack } from "@mantine/core";
4-
import { Navigate } from "react-router";
1+
import { useLobbyNavigation } from "@/app/duel/_hooks/useDuelNavigation";
2+
import { Flex, Divider, Card, Stack } from "@mantine/core";
53

64
import PartyCreate from "./PartyCreate";
75
import PartyJoin from "./PartyJoin";
86

97
export default function PartyEntry() {
10-
const { data, status } = useGetCurrentDuelOrPartyQuery();
11-
12-
if (status === "pending") {
13-
return (
14-
<Flex
15-
direction={"column"}
16-
align={"center"}
17-
justify={"center"}
18-
w={"98vw"}
19-
h={"90vh"}
20-
>
21-
<Loader />
22-
</Flex>
23-
);
24-
}
25-
26-
if (status === "error") {
27-
return (
28-
<ToastWithRedirect to={-1} message={"Sorry, something went wrong."} />
29-
);
30-
}
31-
32-
if (data.success) {
33-
return (
34-
<Navigate to="/duel/current" />
35-
);
36-
}
8+
useLobbyNavigation();
379

3810
return (
3911
<Flex w="100%" h="81vh" justify="center" align="center">
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useMyDuelOrPartyData } from "@/lib/api/queries/duels/sse";
2+
import { useEffect } from "react";
3+
import { useNavigate } from "react-router";
4+
5+
export function useLobbyNavigation() {
6+
const navigate = useNavigate();
7+
const { data } = useMyDuelOrPartyData();
8+
9+
useEffect(() => {
10+
if (data?.success) {
11+
navigate("/duel/current");
12+
} else {
13+
navigate("/duel");
14+
}
15+
}, [data?.success, navigate]);
16+
17+
return;
18+
}

js/src/lib/api/queries/duels/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ export const useCreatePartyMutation = () => {
7777

7878
return useMutation({
7979
mutationFn: createParty,
80-
onSuccess: () => {
81-
queryClient.invalidateQueries({ queryKey: ["party"] });
80+
onSuccess: async () => {
81+
await queryClient.invalidateQueries({ queryKey: ["party"] });
8282
},
8383
});
8484
};

js/src/lib/api/queries/duels/sse/index.ts

Lines changed: 37 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,20 @@ import { ApiURL } from "@/lib/api/common/apiURL";
33
import { fetchEventSource } from "@/lib/api/common/fetchEventSource";
44
import { useGetCurrentDuelOrPartyQuery } from "@/lib/api/queries/duels";
55
import { Api } from "@/lib/api/types";
6-
import { useEffect, useMemo, useState } from "react";
6+
import { useQuery, useQueryClient } from "@tanstack/react-query";
7+
import { useEffect } from "react";
78

8-
type DuelStreamData = {
9-
data: UnknownApiResponse<Api<"DuelData">> | null;
10-
isConnected: boolean;
11-
error: Error | null;
12-
};
13-
14-
type FinalDuelStreamData = DuelStreamData & {
15-
status: "success" | "pending" | "error";
16-
};
17-
18-
export const useMyDuelOrPartyData = (): FinalDuelStreamData => {
19-
const { data, status, error } = useGetCurrentDuelOrPartyQuery();
9+
export const useMyDuelOrPartyData = () => {
10+
const { data } = useGetCurrentDuelOrPartyQuery();
2011
const d = useDuelOrPartyData(data?.payload?.code || "");
2112

22-
if (status === "pending") {
23-
return {
24-
data: null,
25-
isConnected: false,
26-
error: null,
27-
status: "pending",
28-
};
29-
}
30-
31-
if (status === "error") {
32-
return {
33-
data: null,
34-
isConnected: false,
35-
error,
36-
status: "error",
37-
};
38-
}
39-
40-
if (!data.success) {
41-
return {
42-
data: null,
43-
isConnected: false,
44-
error: null,
45-
status: "success",
46-
};
47-
}
48-
4913
return d;
5014
};
5115

52-
export const useDuelOrPartyData = (lobbyCode: string): FinalDuelStreamData => {
53-
const [state, setState] = useState<DuelStreamData>({
54-
data: null,
55-
isConnected: false,
56-
error: null,
16+
export const useDuelOrPartyData = (lobbyCode: string) => {
17+
const queryClient = useQueryClient();
18+
const query = useQuery<UnknownApiResponse<Api<"DuelData">>>({
19+
queryKey: ["duel", lobbyCode],
5720
});
5821

5922
useEffect(() => {
@@ -63,72 +26,38 @@ export const useDuelOrPartyData = (lobbyCode: string): FinalDuelStreamData => {
6326

6427
const controller = new AbortController();
6528

66-
const connect = async () => {
67-
const { url, method, res } = ApiURL.create("/api/duel/{lobbyCode}/sse", {
68-
method: "POST",
69-
params: {
70-
lobbyCode: lobbyCode,
71-
},
72-
});
73-
74-
const controller = new AbortController();
75-
76-
await fetchEventSource(url, {
77-
method,
78-
headers: {
79-
"Content-Type": "application/json",
80-
},
81-
signal: controller.signal,
82-
83-
async onopen(response) {
84-
if (response.ok) {
85-
setState((prev) => ({ ...prev, isConnected: true, error: null }));
86-
return;
87-
}
88-
},
89-
90-
onmessage(ev) {
91-
try {
92-
const data = res(JSON.parse(ev.data));
93-
setState((prev) => ({ ...prev, data: data, isConnected: true }));
94-
} catch (e) {
95-
console.error("Failed to parse SSE message", e);
96-
throw e;
97-
}
98-
},
99-
100-
onerror(err) {
101-
if (err instanceof Error) {
102-
if (err.message) {
103-
setState((prev) => ({ ...prev, error: err, isConnected: false }));
104-
}
105-
}
106-
},
107-
108-
onclose() {
109-
setState((prev) => ({ ...prev, isConnected: false }));
110-
},
111-
});
112-
};
113-
connect();
29+
const { url, method, res } = ApiURL.create("/api/duel/{lobbyCode}/sse", {
30+
method: "POST",
31+
params: {
32+
lobbyCode,
33+
},
34+
});
35+
36+
fetchEventSource(url, {
37+
method,
38+
headers: {
39+
"Content-Type": "application/json",
40+
},
41+
signal: controller.signal,
42+
43+
onmessage(ev) {
44+
try {
45+
const data = res(JSON.parse(ev.data));
46+
queryClient.setQueryData(["duel", lobbyCode], data);
47+
} catch (e) {
48+
console.error("Failed to parse SSE message", e);
49+
}
50+
},
51+
52+
onerror(err) {
53+
console.error("SSE error", err);
54+
},
55+
});
11456

11557
return () => {
11658
controller.abort();
117-
setState((prev) => ({ ...prev, isConnected: false, error: null }));
11859
};
119-
}, [lobbyCode]);
120-
121-
const status = useMemo(() => {
122-
if (state.data && state.isConnected) {
123-
return "success";
124-
}
125-
126-
if (!state.isConnected && state.error) {
127-
return "error";
128-
}
129-
130-
return "pending";
131-
}, [state.data, state.error, state.isConnected]);
60+
}, [lobbyCode, queryClient]);
13261

133-
return { ...state, status };
62+
return query;
13463
};

0 commit comments

Comments
 (0)