본문 바로가기
매일 해내는 개발/Develog

[Develog] 좋아요 기능 구현(with firebase, useQuery, useMutation)

by 해야지 2023. 2. 25.
반응형

현재 데이터 구조상  모든 게시글을 저장하는 post 컬렉션(문서는 각 게시글 데이터)과 내가 좋아요한 게시글이 저장되는 myprojects 컬렉션(문서는 각 유저)으로 나눠져있고
좋아요로 증가 또는 감소한 숫자는 post 컬렉션의 like 필드에 저장되고
내가 좋아한 프로젝트는 myproject 컬렉션의 likedProjects 필드에 배열로 저장이된다.

 

1. 초안

처음에는 좋아요를 누를 때마다 데이터를 업데이트 해주는 방식으로 구현했다.

대신 데이터를 조회하는 건 초기 렌더링 한번만 할 수 있도록 했고 이 후 토글될 때 마다 

클라이언트 사이드에서는 state로 상태를 관리할 수 있도록 했다.

//api/projectDetail.tsx

//좋아요 수 업데이트
export const updateLike = async (pid: any, countLike: number) => {
  const docRef = doc(firestore, 'post', pid);
  await updateDoc(docRef, { like: countLike });
};

// 좋아요한 프로젝트 업데이트
export const updateMyProject = async (
  uid: string,
  pid: string,
  isLiked: boolean | undefined,
) => {
  const docRef = doc(firestore, 'myproject', uid);
  if (isLiked === true) {
    await updateDoc(docRef, { likedProjects: arrayUnion(pid) });
  } else if (isLiked === false) {
    await updateDoc(docRef, { likedProjects: arrayRemove(pid) });
  }
};
//WriterToShare.tsx

  const WriterToShareArea = ({ projectData, pid, userData }: any) => {
  const { uid, like, title, content } = projectData;
  const [countLike, setCountLike] = useState(like);
  const [isLike, setIsLike] = useState<boolean>(); // 관심버튼 클릭시 true/false로 변경, 초기화해주면 동기화 문제 발생
  const { mutate: likeMutate } = useMutation(() => updateLike(pid, countLike));
  const { mutate: likedProjectMutate } = useMutation(() =>
    updateMyProject(uid, pid, isLike),
  );
  const { data: myprojectData } = useQuery({
    queryKey: ['myproject', uid],
    queryFn: () => findWithCollectionName('myproject', uid),
  });

  //현재 사용자가 좋아요를 눌렀는지 확인하는 기능
  useEffect(() => {
    setIsLike(myprojectData?.likedProjects?.includes(pid));
  }, [myprojectData]);

  //isLike가 변경될 때마다 좋아요 수 및 좋아요한 프로젝트를 변경해주는 기능
  useEffect(() => {
    likeMutate(pid, countLike);
    likedProjectMutate(uid, pid);
  }, [isLike]);

  //좋아요 기능
  const handleLike = (event: React.MouseEvent) => {
    event.preventDefault();
    if (isLike === true) {
      setCountLike(countLike - 1);
      // 클릭하기 전 true인 경우
    } else if (isLike === false) {
      //클릭하기 전 false인 경우
      setCountLike(countLike + 1);
    }
    setIsLike(!isLike);
  };

  };
  return (
    <WriterToShareContainer>
      <WriterWrapper>
        {/* 게시글 작성자 프로필 이미지/ Todo: 클릭시 공개프로필로 연결? */}
        <WriterProfileImg src={userData?.photoURL} />
        <WriterNickname>{userData?.displayName ?? `닉네임`}</WriterNickname>
      </WriterWrapper>
      <IconWrapper>
          {/* Todo: 관심버튼 false->true이면 RiHeartAddFill아이콘으로 변경해주고 +1, true->false이면 RiHeartAddLine아이콘으로 변경해주고 -1*/}
          {isLike ? <RiHeartAddFill /> : <RiHeartAddLine />}
          관심 {countLike ?? '없음'}
        </IconButton>
        <IconButton
          onClick={(event) => {
            handleShare(event);
          }}
        >
      </IconWrapper>
    </WriterToShareContainer>
  );
};

 

하지만 이 후 useQuery에 staleTime을 적용하게 되고 로직이 바뀌면서 좋아요 기능이 잘 적용이 안되는 문제가 생겼다.

많은 우여곡절 끝에.. 완성된 코드는 다음과 같다.

2. 최종

//Likes.tsx

  const { uid } = useAuth();
  const { openModal } = useGlobalModal();

  const handleLikeButton = async (event: any) => {
    event.preventDefault();
    if (isLike) {
      setIsLike(false);
      setIsLike(false);
      setCountLike(countLike - 1);
    } else {
      setIsLike(true);
      setIsLike(true);
      setCountLike(countLike + 1);
    }
  };

  //좋아요한 프로젝트 조회
  const { data: myProjects } = useQuery({
    queryKey: ['myProjects', uid],
    queryFn: () => findWithCollectionName('myprojects', uid),
  });

  const { data: projectLike } = useQuery({
    queryKey: ['post', pid],
    queryFn: () => findWithCollectionName('post', pid),
  });

  const [countLike, setCountLike] = useState(projectLike?.like);
  const [isLike, setIsLike] = useState(myProjects?.likedProjects.includes(pid));

  const queryClient = useQueryClient();
  const { mutate: updateLikeMutate } = useMutation(
    () => updateLike(pid, countLike),
    {
      onSettled: () => {
        queryClient.invalidateQueries(['post', pid]);
      },
      onSuccess: () => {
        queryClient.invalidateQueries(['post', pid]);
        setCountLike(projectLike?.like);
      },
    },
  );

  const { mutate: updateMyProjectMutate } = useMutation(
    () => updateMyProject(uid, pid, isLike),
    {
      onSettled: () => {
        queryClient.invalidateQueries(['myProjects', uid]);
      },
      onSuccess: () => {
        queryClient.invalidateQueries(['myProjects', uid]);
      },
    },
  );
  useEffect(() => {
    setCountLike(projectLike?.like);
    // setIsLike(myProjects?.likedProjects.includes(pid));
    return () => {
      updateMyProjectMutate();
      updateLikeMutate();
      setIsLike(myProjects?.likedProjects.includes(pid));
    };
  }, []);
  useEffect(() => {
    setCountLike(projectLike?.like);
  }, [projectLike?.like]);

  useEffect(() => {
    setIsLike(myProjects?.likedProjects.includes(pid));
  }, [myProjects?.likedProjects]);

  return (
    <IconButton
      onClick={(event) => {
        if (!uid) {
          openModal('login', 0);
          return;
        }
        handleLikeButton(event);
      }}
    >
      {isLike ? (
        <FillHeart version={version} />
      ) : (
        <LineHeart version={version} />
      )}
      관심 {countLike ?? ' 0'}
    </IconButton> // 로그아웃인 경우 관심 버튼 클릭 시 likedProjects에 데이터가 없어서 로직 에러 발생: 예외처리 필요
  );
};

 

기존에 props로 상위 컴포넌트에서 useQuery로 조회한 좋아요수(like) 데이터를 가져왔는데 잘 되지 않았다.

db에서는 좋아요 수가 4인데 화면에서는 6이거나... 좋아요 데이터가 잘 들어갔는데 다시 해당 페이지에 들어가서 렌더링되기 전에 데이터가 db에서 삭제됐었다. 

아마 처음에 useEffect로 isLike가 바뀔 때마다 데이터를 업데이트 하게 해줬는데 처음 렌더링 할 때 isLike 값이 캐싱된 데이터가 들어가는건지 아니면 렌더링되기 전에 데이터를 못불러와서 그런지... 아직 원인은 파악중이다..

그래서 좋아요 컴포넌트에서 다시 데이터를 새로 조회하게했고

기존에 토글될 때 마다 데이터를 업데이트 하는 방식에서 페이지가 언마운트될 때 데이터를 업데이트 할 수 있도록 useEffect를 수정했다. 

그리고 데이터 업데이트가 성공하든 말든 onSettled를 추가하여 invalidateQueries를 추가했다.

새로 데이터가 조회될 때마다 좋아요 수와 내가 좋아한 목록 데이터가 있는지 없는지를 저장하게 했다.
하지만 invalidate는 언마운트 될 때마다 실행되니까 데이터가 캐싱되지 않고 새로 저장할 수 있게 해준것이다.

내가 짠 코드이지만 로직에 아직 의문이 있다.. 다시 연구해봐야겠다.

반응형

댓글