티스토리 뷰

 이번 팀 과제에서 제일 애를 먹였던 것은 단연컨대 북마크 기능이다. 튜터님들도 북마크라는 말을 들으면 기겁할 정도로 악명이 높은데, 처음 맡을 땐 그렇게 어려울 줄 몰랐다. 그런데 구현하다 보니 까다롭고, 내가 처음 써보는 데이터 불러오기 기능(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를 사용할 것 같다. 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함