24/1/26 좋아요 기능 구현

2024. 1. 29. 08:34카테고리 없음

심화플러스 주차에 만들었던 좋아요 기능을 리뷰 해보자

<LikeBtn.jsx> 

import React from "react";
import styled from "styled-components";
import HeartEmpty from "../../assets/images/HeartEmpty.png";        //좋아요가 눌린상태인지 여부에
import HeartFull from "../../assets/images/HeartFull.png";                 //따른 이미지 두개
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";  //CUD를 위한 useMutation
import { addHeart, deleteHeart, getHeart } from "./queryFns";                             //과 Read를 위한 useQuery
import { useSelector } from "react-redux";                                   //쿼리펑션들은 따로 보관했다.
import Swal from "sweetalert2";              

export default function LikeBtn({ name, id }) {
  const { isLogin, uid /* email, displayName,  photoURL */ } = useSelector(
    (state) => state.authSlice                //유저정보를 담은 툴킷의 스테이트를 유즈셀렉터로 불러온다.
  );                                                        
 
  // useEffect(() => {
  //   console.log(user, "이거 유저다");
  // }, []);

  // const [like, setLIke] = useState(false);
  const { data: hearts, isLoading } = useQuery({    //유즈 쿼리 사용시 소괄호안에는 객체가 들어오고
    queryKey: ["hearts"],                                    //필수 요소인 쿼리키와 쿼리펑션이 있다.
    queryFn: getHeart,                                //쿼리펑션들을 담은 파일들은 별도 보관했고 밑에서 설명예정
  });
  // console.log(posts, " 데이터야2");
  const queryClient = useQueryClient();                 //유즈 뮤테이션을 사용하기 위한 쿼리클라이언트
  const { mutate: mutateToAdd } = useMutation({         //이것을 설정해야 유즈쿼리의 캐시데이터에 접근가능
    mutationFn: addHeart,                                            //하트추가 함수 쿼리펑션파일에 보관중
    // onSuccess: async () => {
    //   await queryClient.invalidateQueries({ queryKey: ["hearts"] });           //주석처리된부분은
    // },                                                                                                      //일반적인 로직
    onMutate: async (newHeart) => {
      await queryClient.cancelQueries({ queryKey: ["hearts"] });            //좋아요의 디스플레이에 즉각적인 
                                                                                                          //변화를 위한 옵티미스틱 업데이트로직
      const previousHearts = queryClient.getQueryData(["hearts"]);   //일단 현재 캐쉬데이터를 취소
                                                                                                            //쿼리데이터를 이전데이터로 명명
      queryClient.setQueryData(["hearts"], (old) => [...old, newHeart]);      //그리고 새로운 하트를 추가해서
                                                                                                        //캐시데이터로써 세팅
      return { previousHearts };             //이전데이터는 남긴다.
    },

    onError: (err, newHeart, context) => {
      queryClient.setQueryData(["hearts"], context.previousHearts);   //비동기통신에 에러발생시
    },                                                                                   //저장해놨던 예전데이터로 롤백

    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ["hearts"] });   //에러없이 정상적 통신이 완료됐으면
    },                                                                    //onMutate때 세팅했던 값으로 정착
  });

  const { mutate: mutateToDelete } = useMutation({                 //좋아요 삭제로직
    mutationFn: deleteHeart,                                               //요함수도 밑에서 다룸
    // onSuccess: async () => {
    //   await queryClient.invalidateQueries({ queryKey: ["hearts"] });
    // },
    onMutate: async (newHeart) => {
      await queryClient.cancelQueries({ queryKey: ["hearts"] });

      const previousHearts = queryClient.getQueryData(["hearts"]);

      queryClient.setQueryData(["hearts"], (old) => [...old, newHeart]);

      return { previousHearts };
    },

    onError: (err, newHeart, context) => {
      queryClient.setQueryData(["hearts"], context.previousHearts);
    },

    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ["hearts"] });
    },
  });
  // let userId_fake;
  // useEffect(() => {
  //   userId_fake = nanoid();
  // }, []);

  const filteredHearts = hearts?.filter((heart) => {            //유즈쿼리의 하트들을 로그 인한 유저와
    return heart.gameId === id;                                  //아이디가 같은애의 배열을 구한다.
  });

  const filterdHeart1 = hearts?.find((heart) => {              //보여질페이지의 게임에 아이디와
    return heart.gameId === id && heart.uid === uid;     //유저아이디가 같은 배열안의 요소 한개 구한다.
  });
  // console.log(filterdHeart1, "이거 하트들");
  const selectedId = filterdHeart1?.id;                 //
  const likeBTN = () => {
    if (!isLogin) return Swal.fire("로그인 후에 이용이 가능합니다.");  //로그인한사람만 누를수 있다.
    // setLIke(!like);

    if (filterdHeart1) {                  //이미 좋아요를 누른사람은 좋아요가 삭제되고
      mutateToDelete(selectedId);           //selectedId를 인자로 전달한다.
    } else {
      mutateToAdd({ uid, id });         //안누르는 사람은 추가되는 로직
    }                                             //매개변수로 uid와 id를 넘겨준다. 좋아요가 어떤게임에
  };                                                //어떤유저에 관한 것인지 얻기위한 정보
  if (isLoading) {
    return <>로딩중...</>;
  }
  return (
    <>
      {/* <LikeButton $like={like.toString()} onClick={likeBTN}>
        좋아요 버튼
      </LikeButton> */}
      <ImageCount>
        <ImageWrapper name={name} onClick={likeBTN}>
          {filterdHeart1 ? (
            <img id="이미지" name={name} src={HeartFull} alt="꽉찬하트"></img>
          ) : (
            <img id="이미지" name={name} src={HeartEmpty} alt="빈하트"></img>
          )}
        </ImageWrapper>
        <StP f name={name}>
          {filteredHearts?.length}
        </StP>
      </ImageCount>
    </>
  );
}

// const LikeButton = styled.div`
//   cursor: pointer;
//   ${(props) => {
//     switch (props.$like) {
//       case "true":
//         return css`
//           background-color: red;
//         `;
//       default:
//         return css`
//           background-color: white;
//         `;
//     }
//   }}
// `;
const ImageCount = styled.div`
  display: flex;
  align-items: center;
  justify-content: flex-end;
  width: 60%;
  gap: 10px;
`;

const ImageWrapper = styled.div`
  width: 30px;
  height: 30px;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 50%;

  overflow: hidden;
  & img {
    width: 100%;
    height: 100%;
  }
`;

const StP = styled.p`
  color: var(--red);
  font-size: 20px;
  text-align: center;
`;

// 좋아요 버튼 연타시 에러가 나거나 좋아요가 연속 두번 눌리는등의 버그 발생
// 이후 프로젝트에서 좋아요 기능 구현시 useState에 true false 두개로 변화하는 토글 로직을 만들고
//onMutate시작할때 useState의 상태를 false로 바꾸어 버튼을 비활성화 시키고 onSettled의 마지막에
//useState의 상태를 true로 만들어 버튼을 누를수 있게 만듦으로써 해결했다.

<queryFn.js> 쿼리 펑션들을 담아놓은 파일이다.

import {
  collection,                          //데이터베이스는 firebase를 사용했다.
  getDocs,
  doc,
  addDoc,
  deleteDoc,
} from "@firebase/firestore";
import { db } from "../../shared/firebase";

export const getHeart = async () => {
  let data = [];
  const response = await getDocs(collection(db, "hearts"));      //모든 좋아요들을 불러오는 로직
  //   console.log(response.docs, "리스판스야");
  //   data = response.docs;
  response.forEach((doc) => {
    const docData = doc.data();
    // console.log(zzz, "크크크야");
    data.push({ ...docData, id: doc.id });
  });
  //   console.log(data, " 데이터야");
  return data;
};

export const addHeart = async ({ uid, id }) => {                  //좋아요를 추가하는 로직 매개변수로 uid와 게임아이디
  await addDoc(collection(db, "hearts"), {                      //인 id를 받는다.
    uid,
    gameId: id,
    haha: "haha",                       //하하는 내 취향 ㅋㅋ
  });
};

export const deleteHeart = async (selectedId) => {         //지우기위해서 특정해야하는 아이디를 가져온다.
  await deleteDoc(doc(db, "hearts", `${selectedId}`));
};

 // 요 당시에 좋아요 기능은 정석적으로 하기보다. 기본적으로 내 아이디어의 기반해서 구현해본것이기
//때문에 뭔가 어설플수 있다. 조언 해주세요