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 ๋ฅผ ์ฐ๋ฉฐ ์๋ฌ๊ฐ ๋ง์๋ค ..
๊ทผ๋ฐ ์๋ง๋ (์๋์๊ฐ์ด) ์ ์ ๋ก๊ทธ์ธ ์ํ์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ์ํด์คฌ๋๊ฒ ์๋ฌ์ ํ๊ทผ์ด์๋ ๊ฑฐ ๊ฐ๋ค