0228
즐겨찾기 (or 좋아요) 기능은 구현해보고 싶었지만 한번도 시도해본 적이 없었다. 다른 분들이 하신 걸 보면서 우와 어떻게 한거지, 생각만 했던 거 같다. 근데 이번 기회에 구현해볼 수 있게 돼서 넘넘 뿌듯하고 좋은 경험이었다 !! ㅠ^ㅠ
사실 로그인/회원가입을 맡아주신 팀원분이 session stroage에 유저 uid를 set하고, 내가 그걸 get해서 쓰는 식으로, firestore를 활용해 데이터 넣고 가져오고 하면 될 거라고 잘 알려주시고, 필드값도 어떻게 할 건지 잘 전달해주셔서 정말 수월하게 된 것 같다. 세션스토리지는 처음 써봤는데, 로컬스토리지와 쓰는 방법이 똑같았다. 또 파이어스토어는 이전에 활용한 경험이 있어서, 다시 구글링해서 잘 써먹은 거 같다.
(근데 배열에 원소 추가, 삭제 하는 건 arrayUnion arrayRemove 라는 메서드를 따로 import 해서 써야되는 건 첨 알았다.. 그것도 모르고 [... 연산자 써서 시도해보다가 왜 안되지, 했음..!)
또, react query 도 처음으로 써보고, useEffect 도 이번기회에 새삼 다시 공부하는 기회가 됐다. (이 훅을 잘 써먹는게 중요한거같다. 의존성배열도 중요..) 그 과정에서 에러도 많이 마주했지만, 구글링도 하고 많이 배웠다
그리고 항상 undefined 에 대한 처리를 해주는 게 중요하다는 걸 깨닫고 있기도 하다. 많은 에러들이 여기서 생겨나는 거 같다. 물론 undefined - 값이 안들어오거나 하면 에러가 나는 게 당연하긴 한데, 그런 의미? 말고도, undefined 로 값이 들어오는 경우에 대한 처리!! 가 정말 중요하다는 느낌이다.
.. const [userUid, setUserUid] = useState('');
const isLogin = useSelector((state) => state.loginReducer);
..
useEffect(() => {
if (!data) return;
if (sessionStorage.getItem('uid')) {
setUserUid(sessionStorage.getItem('uid'));
}
...
}, [data, sortBy, isLogin]);
..
return (
{userUid ? (
<ListFavoriteButton userUid={userUid} channelId={channel.channelId} />
) : (
<NonFavStar src={nonFavImg} width={30} onClick={handleNonUserFavClick} />
)}
..
위의 cardList.jsx 에서 ( .. 은 생략된 부분)
전역상태관리된 isLogin 은 useEffect 의존성 배열에 넣어줬다.
세션스토리지에서 uid를 받아와 useState set을 해줬다.
근데 이 uid를 전역 상태 관리해주는게 좋을 거 같다고 생각하긴 했는데 (여러페이지에서 사용하므로)
사실 그냥 세션스토리지에서 바로 꺼내면 돼서 별 문제는 없는 거 같다.
그리고 컴포넌트 리턴문 안에서는, 받아온 userUid의 존재여부에 따라, 있으면 즐겨찾기(색칠된 별이미지/빈별 토글 로 나타나는) 컴포넌트로, 그게 아니면 (비로그인 -) empty 별 이미지를 띄우도록 했다.
즐겨찾기 컴포넌트 는 아래에!
로그인상태인 경우에 뜨게 함
// ListFavoriteButton.jsx
import React, { useEffect, useState } from 'react';
import nonFavImg from '../../assets/emptyStar.png';
import favImg from '../../assets/coloredStar.png';
import styled from 'styled-components';
import { useQuery } from '@tanstack/react-query';
import { addFavoriteChannel, fetchIsFavorite, removeFavoriteChannel } from '../../api/favorites';
import Loading from '../layout/Loading';
import Error from '../../pages/Error';
export const ListFavoriteButton = ({ userUid, channelId }) => {
const [favorite, setFavorite] = useState(false);
// 기존 즐겨찾기 데이터 가져와서 별표 뜨게하기 => RQ
const {
data: favoriteChannels,
isLoading,
error
} = useQuery({
queryKey: ['favoriteChannels', userUid, favorite],
queryFn: () => fetchIsFavorite(userUid)
});
useEffect(() => {
favoriteChannels?.includes(channelId) ? setFavorite(true) : setFavorite(false);
}, [favoriteChannels, favorite]);
const toggleFavoriteClick = async () => {
if (!favorite) {
// 추가
try {
await addFavoriteChannel(userUid, channelId);
setFavorite(true);
} catch (error) {
alert('즐겨찾기 추가가 제대로 되지 않았어요. 다시 시도해주세요 !');
}
} else {
// 삭제(해제)
try {
await removeFavoriteChannel(userUid, channelId);
setFavorite(false);
} catch (error) {
alert('즐겨찾기 삭제가 제대로 되지 않았어요. 다시 시도해주세요 !');
}
}
};
if (isLoading) return <Loading />;
if (error) return <Error />;
return (
<div>
{/* 즐겨찾기 (별) 눌렀을 때 (toggle - 즐겨찾기 추가/해제) */}
<NonFavStar src={favorite ? favImg : nonFavImg} width={30} onClick={toggleFavoriteClick} />
</div>
);
};
const NonFavStar = styled.img`
cursor: pointer;
`;
즐겨찾기 상태는 useState를 사용했다. 사실 이게 여러 페이지에서 사용될 거라서, 전역상태관리를 해야하나 생각했는데.. 내가 생각한게 잘못된건진 모르겠지만.. ㅠㅠ 전역 상태관리를 해버리면, 이게 다 통용?? 돼서, 한 목록당 즐찾이 아니라 하나를 누르면 다같이 토글이 되어버리는 식이라서.. 여튼 각각의 상태가 있어야 한다고 생각했고 그래서 한 컴포넌트에 state를 하나씩 주기로 했다.
또 리액트쿼리를 사용하는 과정에서 , 처음엔 수월해서 쉽다고 생각했지만 다른경우들을 마주하면서 에러가 팡팡ㅠ^ㅠ
수정하면서 애먹었지만 많이 배우기도 했다.
저 위에서 쓰인 파이어스토어 DB 관련 함수들은 따로 favorite.js에 있다 아래참고
import { arrayRemove, arrayUnion, doc, getDoc, updateDoc } from 'firebase/firestore/lite';
import db from './config';
// 즐겨찾기 목록 DB에서 가져오기
export const fetchIsFavorite = async (userUid) => {
if (!userUid) return '';
try {
const userRef = doc(db, 'users', userUid);
const userSnap = await getDoc(userRef);
if (userSnap.exists()) {
return userSnap.data().favChannels;
}
return '';
} catch (error) {
console.log(`cannot fetch user's favorites from firestore : `, error);
}
};
// 즐겨찾기 추가
export const addFavoriteChannel = async (userUid, channelId) => {
try {
await updateDoc(doc(db, 'users', userUid), {
favChannels: arrayUnion(channelId)
});
} catch (error) {
console.error('cannot add favorite to firestore: ', error);
}
};
// 즐겨찾기 삭제
export const removeFavoriteChannel = async (userUid, channelId) => {
try {
await updateDoc(doc(db, 'users', userUid), {
favChannels: arrayRemove(channelId)
});
} catch (error) {
console.error('cannot remove favorite from firestore: ', error);
}
};
그리고 상세페이지에서도 즐찾이 유지되도록 하고, 똑같이 버튼을 구현했다.
특히 이때리액트 쿼리를 쓰고 useEffect 를 쓰며 에러가 많았다 ..
근데 아마도 (아래와같이) 유저로그인 상태에 대한 처리를 안해줬던게 에러의 화근이었던 거 같다