티스토리 뷰
이번 팀 과제에서 제일 애를 먹였던 것은 단연컨대 북마크 기능이다. 튜터님들도 북마크라는 말을 들으면 기겁할 정도로 악명이 높은데, 처음 맡을 땐 그렇게 어려울 줄 몰랐다. 그런데 구현하다 보니 까다롭고, 내가 처음 써보는 데이터 불러오기 기능(axios, try-catch, json-server 다루기 등)들이 너무 많이 쓰였다. 어려운 것을 알고 맡았다면 덜 급했겠지만 모르고 맡았기 때문에 시간이 촉박했다. 튜터님들과 불같은 토론을 통해서 발전한 내 새끼(코드)를 여기에 기록해두려고 한다.
1. 북마크 기능 구현의 방향

로직 자체는 간단했다. 로그인 체크 + 유저정보 빼 오고 > 저장하기 누르면 저장하고 (axios post 써서 데이터 작성하고) > 취소하기 누르면 취소되고(axios delete 써서 데이터 지우고)가 전부였다.
더 필요한 지식은 axios, zuStand 활용법 정도였다. axios는 강의 내용을 뒤지며 적었고 zuStand는 처음부터 공부하기엔 너무 많아 필요한 부분만 팀원분이 알려주신 내용을 바탕으로 활용했다. 더듬더듬 적은 첫 번째 코드.
import useAuthStore from "@/core/store/authStore";
import axios from "axios";
import { useState } from "react";
export function BookmarkButton({ festival, bookmarkList }) {
const [bookmarkOn, setBookmarkOn] = useState(false);
const { isLoggedIn, userId } = useAuthStore();
const isBookmarked = bookmarkList.some((item) => {
return festival.fstvlNm === item.fstvlNm && userId === item.userId;
});
console.log(isBookmarked);
if (isLoggedIn) {
const saveBookmark = async () => {
try {
const response = await axios.post("http://localhost:4000/bookmarkFestivalList", {
...festival,
userId
});
console.log("API Response :", response.data);
if (response.data.result === "Created") {
setBookmarkOn(!bookmarkOn);
}
} catch (error) {
console.log("북마크 저장 실패");
}
};
const deleteBookmark = async () => {
console.log(festival.id);
try {
const getList = await axios.get(`http://localhost:4000/bookmarkFestivalList`);
console.log(getList.data);
await axios.delete(`http://localhost:4000/bookmarkFestivalList/${festival.id}`);
setBookmarkOn(!bookmarkOn);
} catch (error) {
console.log("북마크 취소 실패");
}
};
return (
<>
{isBookmarked ? (
<button onClick={() => deleteBookmark(festival)}>취소하기</button>
) : (
<button onClick={() => saveBookmark(festival)}>저장하기</button>
)}
</>
);
} else {
const notLoginClick = () => {
alert("로그인이 필요합니다.");
};
return <button onClick={() => notLoginClick()}>저장하기</button>;
}
}
2. 발생한 문제
2-1. delete를 하려면 유저의 정보와 축제의 정보 두 가지의 조건이 맞아떨어지는 filter가 필요했다. 그런데 json-server에서는 delete를 하려면 id만으로만 가능하다고 한다.
2-2. delete를 하기 위해서는 get을 새로 또 하고 delete를 하는 번거로운 방법을 사용해야 했다.
2-3. tanstackquery를 배우긴 했지만 한번도 실전에서 써먹어 본 적은 없는 내가 열심히 무시하고 있지만, 코드가 확실히 복잡하다. 나도, 튜터님들도 tanstackquery를 사용하면 코드가 더 간결해지고 간편해질 것을 안다.
3. 해결 방법
어차피 써야 할 것이라면 피해서 뭘 하겠나. 시간이 촉박하지만 열심히 공부해왔던 것을 믿고 Refactoring을 하는 수 밖에.
코드 전체를 tanstackquery로 리팩토링했다.
import useAuthStore from "@/core/store/authStore";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
import { useState } from "react";
export function BookmarkButton({ festival, bookmarkList }) {
const [bookmarkOn, setBookmarkOn] = useState(false);
const { isLoggedIn, userId } = useAuthStore();
const queryClient = useQueryClient();
const isBookmarked = bookmarkList.some((item) => {
return festival.fstvlNm === item.fstvlNm && userId === item.userId;
});
console.log(isBookmarked);
if (isLoggedIn) {
const { data, isPending, isError } = useQuery({
queryKey: ["bookmarkFestivalList"],
queryFn: () => axios.get("http://localhost:4000/bookmarkFestivalList")
});
const saveBookmark = useMutation({
mutationFn: () =>
axios.post("http://localhost:4000/bookmarkFestivalList", {
...festival,
userId
}),
onSuccess: () => {
setBookmarkOn(!bookmarkOn);
queryClient.invalidateQueries("bookmarkFestivalList");
}
});
const deleteBookmark = useMutation({
mutationFn: () => axios.delete(`http://localhost:4000/bookmarkFestivalList/3bb3ce2f-634d-d33c-50fa-9eddbd1009e4`),
onSuccess: () => {
setBookmarkOn(!bookmarkOn);
queryClient.invalidateQueries("bookmarkFestivalList");
}
});
if (isPending) {
return <div>로딩중입니다...</div>;
}
if (isError) {
return <div>데이터 조회 중 오류가 발생했습니다.</div>;
}
return (
<>
{isBookmarked ? (
<button onClick={() => deleteBookmark.mutate(festival)}>취소하기</button>
) : (
<button onClick={() => saveBookmark.mutate(festival)}>저장하기</button>
)}
</>
);
} else {
const notLoginClick = () => {
alert("로그인이 필요합니다.");
};
return <button onClick={() => notLoginClick()}>저장하기</button>;
}
}
tanstackQuery는 생각보다 쉬웠고, 또 엄청 간편했다! 리팩토링을 하면서 문제 2-2와 2-3이 말끔히 없어졌다.
남은 건 문제 2-1, delete 함수를 처리하는 방법이다.
4. 두 가지 조건을 만족시키는 delete 함수를 json-server에서 구현하는 법
delete 함수를 구현하며 많은 고민이 있었다. json-server의 공식 문서에서는 id만 된다고 하고, 나는 festival.jsx에서 임의로 key값을 위해 uuid로 이루어진 id를 넣고 있었는데, 이게 새로고침을 할 때마다 달라져서 db값과 일치하지가 않았다. 주말에도 친구랑 만나서 밥을 먹다가 (친구는 개발에는 문외한이다.) 이러이러한 문제가 있다고 말해봤는데, 친구가 잠시 생각하더니 두 가지를 합쳐서 넣으면 되지 않느냐고 말했다.
머리를 망치로 친 기분이었다. 그래. 굳이 따로 넣을 필요가 없다! id값만을 이용해야 한다면, 하나로 뭉친 두 조건을 id값으로 넣어버리면 될 일이다. 기존의 id값을 빼고 post를 할 때 축제 이름+userid를 합친 id값을 새로 넣었다.
import useAuthStore from "@/core/store/authStore";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
export function BookmarkButton({ festival, bookmarkList, refetchingFestivalList }) {
const { isLoggedIn, userId } = useAuthStore();
const queryClient = useQueryClient();
const isBookmarked = bookmarkList.some((item) => {
return festival.fstvlNm === item.fstvlNm && userId === item.userId;
});
console.log(isBookmarked);
if (isLoggedIn) {
const { data, isPending, isError } = useQuery({
queryKey: ["bookmarkFestivalList"],
queryFn: () => axios.get("http://localhost:4000/bookmarkFestivalList")
});
const saveBookmark = useMutation({
mutationFn: () =>
axios.post("http://localhost:4000/bookmarkFestivalList", {
...festival,
userId,
id: `${festival.fstvlNm}${userId}`
}),
onSuccess: () => {
refetchingFestivalList();
queryClient.invalidateQueries("bookmarkFestivalList");
}
});
const deleteBookmark = useMutation({
mutationFn: () => axios.delete(`http://localhost:4000/bookmarkFestivalList/${festival.fstvlNm}${userId}`),
onSuccess: () => {
refetchingFestivalList();
queryClient.invalidateQueries("bookmarkFestivalList");
}
});
console.log(festival.id);
if (isPending) {
return <div>로딩중입니다...</div>;
}
if (isError) {
return <div>데이터 조회 중 오류가 발생했습니다.</div>;
}
return (
<>
{isBookmarked ? (
<button onClick={() => deleteBookmark.mutate(festival)}>취소하기</button>
) : (
<button onClick={() => saveBookmark.mutate(festival)}>저장하기</button>
)}
</>
);
} else {
const notLoginClick = () => {
alert("로그인이 필요합니다.");
};
return <button onClick={() => notLoginClick()}>저장하기</button>;
}
}
delete 함수가 잘 작동한다.
5. 회고
5-1. 좋았던 점
이번 주차에서 새롭게 배운 내용을 적용할 수 있었다. 아무래도 프로젝트에서 적용하는 거랑 배우는 과정에서 실습하는 거랑은 다르다 보니 스스로 활용하고 적용하는 과정에서 더 공부하게 되어 좋았다.
또한 어려운 기능에 도전하게 되면서 튜터님들이 말하는 복잡한 기능이 이정도 난이도구나 하는 감이 잡힌 것 같다. 그리고 많이 애먹긴 했지만 끝나고 보니 별로 안 어려운 것 같아 자신감이 생겼다.
기능을 구현하면서 코딩을 하고 있지 않을 때도 어떻게 하면 문제를 해결할 수 있을까 하는 생각을 하게 되는 것 같다. 점점 개발자가 되어간다.
5-2. 아쉬웠던 점
TanstackQuery를 처음부터 적용했으면 어땠을까 하는 아쉬움이 있다. 하지만 사용하지 않고 불편함을 겪어봤으니까 이제 대부분의 경우에는 TanstackQuery를 사용할 것 같다.
'스파르타 > 팀과제, 개인과제' 카테고리의 다른 글
팀과제 가취뽀 - supabase 데이터 업데이트 안되는 문제 트러블 슈팅 (5) | 2024.10.14 |
---|---|
TypeScript에서 useSearchParams를 쓸 때 나오는 오류 (1) | 2024.10.08 |
가을축제 팀과제 - uuid로 넣은 key값 리팩토링하기 (0) | 2024.09.20 |
미루는 습관 고치기 2 (0) | 2024.09.11 |
팀과제 - 뉴스피드 만들기 (2. 수정 코드 구현하기) (0) | 2024.09.05 |