diff --git a/package.json b/package.json
index 5706a04..cc5ec03 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@fontsource/roboto": "^5.0.8",
"@mui/icons-material": "^5.15.4",
"@mui/material": "^5.15.4",
+ "@tanstack/react-query": "^5.51.11",
"@types/react-router-dom": "^5.3.3",
"dayjs": "^1.11.11",
"prettier": "^3.3.0",
diff --git a/src/App.tsx b/src/App.tsx
index 9f7de79..5e4690d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -11,35 +11,40 @@ import Board from "./modules/Community/index.tsx";
import BoardView from "./modules/Community/BoardView.tsx";
import { Container, CssBaseline } from "@mui/material";
import { createTheme, ThemeProvider } from "@mui/material/styles";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
function App() {
const defaultTheme = createTheme();
+ const queryClient = new QueryClient();
+
return (
-
-
-
-
-
-
-
- } />
- } />
- } />
- } />
- } />
- {/*
+
+
+
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ {/*
*/}
-
-
-
-
-
+
+
+
+
+
+
);
}
diff --git a/src/api/bingo_api.ts b/src/api/bingo_api.ts
index b798a10..0ffb4b9 100644
--- a/src/api/bingo_api.ts
+++ b/src/api/bingo_api.ts
@@ -1,4 +1,5 @@
-const API_URL: string = import.meta.env.VITE_API_URL;
+// const API_URL: string = import.meta.env.VITE_API_URL;
+const API_URL: string = 'http://localhost:8000';
export const singUpUser = async (username: string) => {
const response = await fetch(
@@ -25,12 +26,13 @@ export const getUser = async (username: string) => {
return data;
};
-export const createBingoBoard = async (
+export const createBingoBoard = async (args: {
userId: string,
boardData: {
[key: string]: { value: string; status: number; selected: number };
}
-) => {
+}) => {
+ const { userId, boardData } = args;
const response = await fetch(`${API_URL}/api/bingo/boards`, {
method: "POST",
headers: {
@@ -42,6 +44,10 @@ export const createBingoBoard = async (
};
export const getBingoBoard = async (userId: string) => {
+ if (!userId) {
+ return;
+ }
+
const response = await fetch(`${API_URL}/api/bingo/boards/${userId}`);
if (response.ok === false) {
return [];
@@ -52,6 +58,7 @@ export const getBingoBoard = async (userId: string) => {
...boardData[key],
id: key,
}));
+ console.log(items);
return items;
};
@@ -83,11 +90,12 @@ export const updateBingoBoard = async (
return response.ok;
};
-export const createUserBingoInteraction = async (
- word_id_list: string | null,
- send_user_id: number,
- receive_user_id: number
-) => {
+export const createUserBingoInteraction = async (args: {
+ word_id_list: string | null;
+ send_user_id: number;
+ receive_user_id: number;
+}) => {
+ const { word_id_list, send_user_id, receive_user_id } = args;
const response = await fetch(`${API_URL}/api/bingo/interactions`, {
method: "POST",
headers: {
@@ -99,6 +107,11 @@ export const createUserBingoInteraction = async (
};
export const getUserLatestInteraction = async (userId: string) => {
+
+ if (!userId) {
+ return;
+ }
+
const response = await fetch(`${API_URL}/api/bingo/interactions/${userId}`);
if (response.ok === false) {
@@ -109,6 +122,10 @@ export const getUserLatestInteraction = async (userId: string) => {
};
export const getUserName = async (userId: string) => {
+ if (!userId) {
+ return;
+ }
+
const response = await fetch(`${API_URL}/api/auth/bingo/get-user/${userId}`);
if (response.ok === false) {
return [];
diff --git a/src/hooks/bingo/mutations/useCreateBingoBoardMutation.ts b/src/hooks/bingo/mutations/useCreateBingoBoardMutation.ts
new file mode 100644
index 0000000..1b70af6
--- /dev/null
+++ b/src/hooks/bingo/mutations/useCreateBingoBoardMutation.ts
@@ -0,0 +1,27 @@
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { createBingoBoard } from "../../../api/bingo_api";
+import QueryKeyGenerator from "../../common/QueryKeyGenerator";
+
+export const useCreateBingoBoardMutation = async (sendUserId: string) => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (args: {
+ userId: string,
+ boardData: {
+ [key: string]: { value: string; status: number; selected: number };
+ },
+ }) => {
+ const { userId, boardData } = args;
+ return createBingoBoard(
+ {
+ userId,
+ boardData,
+ }
+ );
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: QueryKeyGenerator.bingoBoard(sendUserId) });
+ queryClient.invalidateQueries({ queryKey: QueryKeyGenerator.userLatestInteraction(sendUserId) });
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/hooks/bingo/mutations/useCreateUserBingoInteractionMutation.ts b/src/hooks/bingo/mutations/useCreateUserBingoInteractionMutation.ts
new file mode 100644
index 0000000..435470b
--- /dev/null
+++ b/src/hooks/bingo/mutations/useCreateUserBingoInteractionMutation.ts
@@ -0,0 +1,29 @@
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { createUserBingoInteraction } from "../../../api/bingo_api";
+import QueryKeyGenerator from "../../common/QueryKeyGenerator";
+
+export const useCreateUserBingoInteractionMutation = async (sendUserId: string) => {
+ const queryClient = useQueryClient();
+ // 새로운 interaction을 생성하는 mutation입니다.
+ return useMutation({
+ mutationFn: (args: {
+ word_id_list: string | null,
+ send_user_id: number,
+ receive_user_id: number
+ }) => {
+ const { word_id_list, send_user_id, receive_user_id } = args;
+ return createUserBingoInteraction(
+ {
+ word_id_list,
+ send_user_id,
+ receive_user_id
+ }
+ );
+ },
+ onSuccess: () => {
+ // 새로운 interaction이 생성되면, 해당 유저의 bingo board와 latest interaction을 업데이트합니다.
+ queryClient.invalidateQueries({ queryKey: QueryKeyGenerator.bingoBoard(sendUserId) });
+ queryClient.invalidateQueries({ queryKey: QueryKeyGenerator.userLatestInteraction(sendUserId) });
+ }
+ });
+};
diff --git a/src/hooks/bingo/useBingoBoardByUserId.ts b/src/hooks/bingo/useBingoBoardByUserId.ts
new file mode 100644
index 0000000..db94091
--- /dev/null
+++ b/src/hooks/bingo/useBingoBoardByUserId.ts
@@ -0,0 +1,16 @@
+import { useQuery } from "@tanstack/react-query";
+import QueryKeyGenerator from "../common/QueryKeyGenerator";
+import { getBingoBoard } from "../../api/bingo_api";
+
+export const useBingoBoardByUserId = (userId: string) => {
+ return useQuery({
+ queryKey: QueryKeyGenerator.bingoBoard(userId), // 쿼리 키를 통해서 쿼리들을 구분합니다. 나중에 쿼리키를 활용해서 해당 쿼리들을 reset 할 수 있습니다.
+ queryFn: () => { // 쿼리 함수
+ const data = getBingoBoard(userId)
+ if (!data) {
+ throw new Error("Bingo board not found");
+ }
+ return data;
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/hooks/bingo/useUserLatestInteractionByUserId.ts b/src/hooks/bingo/useUserLatestInteractionByUserId.ts
new file mode 100644
index 0000000..02f9595
--- /dev/null
+++ b/src/hooks/bingo/useUserLatestInteractionByUserId.ts
@@ -0,0 +1,16 @@
+import { useQuery } from "@tanstack/react-query";
+import QueryKeyGenerator from "../common/QueryKeyGenerator";
+import { getUserLatestInteraction } from "../../api/bingo_api";
+
+export const useUserLatestInteractionByUserId = (userId: string) => {
+ return useQuery({
+ queryKey: QueryKeyGenerator.userLatestInteraction(userId),
+ queryFn: () => {
+ const data = getUserLatestInteraction(userId)
+ if (!data) {
+ throw new Error("User latest interaction not found");
+ }
+ return data;
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/hooks/common/QueryKeyGenerator.ts b/src/hooks/common/QueryKeyGenerator.ts
new file mode 100644
index 0000000..fd0e81f
--- /dev/null
+++ b/src/hooks/common/QueryKeyGenerator.ts
@@ -0,0 +1,5 @@
+export default {
+ user: (username: string) => ['user', username],
+ bingoBoard: (userId: string) => ['bingoBoard', userId],
+ userLatestInteraction: (userId: string) => ['userLatestInteraction', userId],
+}
\ No newline at end of file
diff --git a/src/hooks/user/useUserById.ts b/src/hooks/user/useUserById.ts
new file mode 100644
index 0000000..5003636
--- /dev/null
+++ b/src/hooks/user/useUserById.ts
@@ -0,0 +1,16 @@
+import { useQuery } from "@tanstack/react-query";
+
+import { getUser as getUserByUsername } from "../../api/bingo_api";
+import QueryKeyGenerator from "../common/QueryKeyGenerator";
+
+export const useUserByUsername = (username: string) => {
+ return useQuery({
+ queryKey: QueryKeyGenerator.user(username), queryFn: () => {
+ const data = getUserByUsername(username)
+ if (!data) {
+ throw new Error("User not found");
+ }
+ return data;
+ }
+ });
+};
\ No newline at end of file
diff --git a/src/modules/Bingo/BingoContainer.tsx b/src/modules/Bingo/BingoContainer.tsx
index c1dabbf..a358cd8 100644
--- a/src/modules/Bingo/BingoContainer.tsx
+++ b/src/modules/Bingo/BingoContainer.tsx
@@ -16,6 +16,13 @@ import {
defafultBingoBoard,
shuffleArray,
} from "./components/DefaultBingoBoard.ts";
+import { useUserByUsername } from "../../hooks/user/useUserById.ts";
+import { useBingoBoardByUserId } from "../../hooks/bingo/useBingoBoardByUserId.ts";
+import { useUserLatestInteractionByUserId } from "../../hooks/bingo/useUserLatestInteractionByUserId.ts";
+import { useCreateUserBingoInteractionMutation } from "../../hooks/bingo/mutations/useCreateUserBingoInteractionMutation.ts";
+import { useQueryClient } from "@tanstack/react-query";
+import QueryKeyGenerator from "../../hooks/common/QueryKeyGenerator.ts";
+import { useCreateBingoBoardMutation } from "../../hooks/bingo/mutations/useCreateBingoBoardMutation.ts";
const useInput = (initialValue: string) => {
const [value, setValue] = useState(initialValue);
@@ -25,10 +32,9 @@ const useInput = (initialValue: string) => {
return { value, onChange };
};
-const BingoContainer = () => {
+const BingoContainer = () => {
const location = useLocation();
- if (location.search === "?logout")
- {
+ if (location.search === "?logout") {
localStorage.setItem("myWordList", "");
localStorage.setItem("recentWords", "");
localStorage.setItem("recentSendUser", "");
@@ -51,8 +57,38 @@ const BingoContainer = () => {
const [opponentID, setOpponentID] = useState("");
const [recentWords, setRecentWords] = useState(localStorage.getItem("recentWords") || "");
const [recentSendUser, setRecentSendUser] = useState(localStorage.getItem("recentSendUser") || "");
- const MyID = useInput(localStorage.getItem("myID") || "");
const [userSelectedWords, setUserSelectedWords] = useState([]);
+ const queryClient = useQueryClient();
+
+ const MyID = useInput(localStorage.getItem("myID") || "");
+ const { data: user, isLoading: userLoading } = useUserByUsername(MyID.value);
+ const { data: opponent, isLoading: opponentLoading } = useUserByUsername(opponentID);
+ const { data: myBingoBoard, isLoading: bingoBoardLoading } = useBingoBoardByUserId(user?.user_id);
+ const { data: userInteraction, isLoading: userInteractionLoading } = useUserLatestInteractionByUserId(user?.user_id);
+ const createUserBingoInteraction = useCreateUserBingoInteractionMutation(MyID.value);
+ const createBingoBoard = useCreateBingoBoardMutation(MyID.value);
+
+ useEffect(() => {
+ if (userInteraction) {
+ const fetchData = async () => {
+ const sendUserName = await getUserName(userInteraction.send_user_id);
+ const wordList = userInteraction.word_id_list;
+ localStorage.setItem("recentWords", wordList);
+ localStorage.setItem("recentSendUser", sendUserName);
+ setRecentWords(wordList);
+ setRecentSendUser(sendUserName);
+ };
+ fetchData();
+ }
+ }, [userInteraction]);
+
+ useEffect(() => {
+ if (myBingoBoard) {
+ setBingoWords(myBingoBoard);
+ }
+ }, [myBingoBoard]);
+
+
const initBingoBoard = async () => {
const boardData: {
[key: string]: { value: string; status: number; selected: number };
@@ -69,8 +105,7 @@ const BingoContainer = () => {
if (MyID.value != "") {
const result = await singUpUser(MyID.value);
- if (result === false && !confirm("이미 누군가 사용중인 계정입니다. 정말 로그인하시겠습니까?") && !confirm("정말 로그인하시겠습니까???"))
- {
+ if (result === false && !confirm("이미 누군가 사용중인 계정입니다. 정말 로그인하시겠습니까?") && !confirm("정말 로그인하시겠습니까???")) {
localStorage.setItem("myWordList", "");
localStorage.setItem("recentWords", "");
localStorage.setItem("recentSendUser", "");
@@ -79,35 +114,19 @@ const BingoContainer = () => {
}
const user = await getUser(MyID.value);
- await createBingoBoard(user.user_id, boardData);
+ await (await createBingoBoard).mutateAsync({ userId: user.user_id, boardData });
}
};
- const refreshBingoWords = async () => {
- const user = await getUser(MyID.value);
- const newBingoWords = await getBingoBoard(user.user_id);
- const userLatestInteraction = await getUserLatestInteraction(user.user_id);
-
- if (userLatestInteraction) {
- const sendUserName = await getUserName(userLatestInteraction.send_user_id);
- const wordList = userLatestInteraction.word_id_list
- localStorage.setItem("recentWords", wordList);
- localStorage.setItem("recentSendUser", sendUserName);
- setRecentWords(wordList);
- setRecentSendUser(sendUserName);
- }
- setBingoWords(newBingoWords);
- };
const sendMyWords = async () => {
- const user = await getUser(MyID.value);
- const opponent = await getUser(opponentID);
- updateBingoBoard(user.user_id, opponent.user_id);
+ updateBingoBoard(user.user_id, opponent.user_id); // 이것도 mutation으로 바꿀 수 있음
const myWords = localStorage.getItem("myWordList");
- const res = await createUserBingoInteraction(
- myWords,
- user.user_id,
- opponent.user_id
+ const res = await (await createUserBingoInteraction).mutateAsync(
+ {
+ word_id_list: myWords,
+ send_user_id: user.user_id,
+ receive_user_id: opponent.user_id
+ }
);
-
return res;
};
const handleInputChange = (event: React.ChangeEvent) => {
@@ -118,18 +137,6 @@ const BingoContainer = () => {
localStorage.setItem("myID", event.target.value);
};
- useEffect(() => {
- const fetchData = async () => {
- const user = await getUser(MyID.value);
- if (user.user_id === null)
- return
- const fetchedBingoWords = await getBingoBoard(user.user_id);
- const fetchedSelectedWords = await getSelectedWords(user.user_id);
- setBingoWords(fetchedBingoWords);
- setUserSelectedWords(fetchedSelectedWords);
- };
- fetchData();
- }, []);
return (
{
recentSendUser={recentSendUser}
handleWordChange={handleWordChange}
handleMyIDChange={handleMyIDChange}
- onRefreshBingoWords={refreshBingoWords}
+ onRefreshBingoWords={() => queryClient.invalidateQueries({ queryKey: QueryKeyGenerator.bingoBoard(MyID.value) })}
onClickSendWords={sendMyWords}
handleInputChange={handleInputChange}
onClickButton={initBingoBoard}