좋아요... 좋아요 기능을 추가해야한다...
어떻게 해야하는지 엄청 찾아봤지만 기본 로컬데이터를 이용해서 관리하기는 어렵고
파이어베이스를 이용한김에 데이터 관리도 로컬이 아닌 파이어베이스로 변경했다.
https://hhyj0000.tistory.com/184
[Next 고캠핑] 로컬 데이터 파이어베이스로 변경하기
캠핑장 좋아요 기능을 추가하기 위해서 로컬로 데이터를 불러왔던것을 firebase로 바꿨다.오른쪽 상단 더보기를 눌러서 json 가져오기를 누르면 내 데이터가 잘 들어와진 것을 확인할 수 있다.
hhyj0000.tistory.com
likeList 컬렉션을 만들었다.
여기에 userId를 추가해서 유저의 좋아요 상태 관리를 할 것.
https://firebase.google.com/docs/firestore/query-data/get-data?hl=ko
Cloud Firestore로 데이터 가져오기 | Firebase
의견 보내기 Cloud Firestore로 데이터 가져오기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 세 가지 방법으로 Cloud Firestore에 저장된 데이터를 검색할 수 있
firebase.google.com
const likeListItem = collection(db, "likeList");
- firestore 데이터베이스에 있는 likeList 컬렉션을 참조한다.
* 파이어베이스 데이터 추가
// 좋아요 추가
export const addLike = async (campingItem: ICampingList, userId: string) => {
try {
await addDoc(likeListItem, {
userId,
campingItem: {
facltNm: campingItem.facltNm,
lineIntro: campingItem.lineIntro,
intro: campingItem.intro,
addr1: campingItem.addr1,
firstImageUrl: campingItem.firstImageUrl,
themaEnvrnCl: campingItem.themaEnvrnCl,
tel: campingItem.tel,
contentId: campingItem.contentId,
lctCl: campingItem.lctCl,
induty: campingItem.induty,
doNm: campingItem.doNm,
sigunguNm: campingItem.sigunguNm,
direction: campingItem.direction,
brazierCl: campingItem.brazierCl,
sbrsCl: campingItem.sbrsCl,
sbrsEtc: campingItem.sbrsEtc,
homepage: campingItem.homepage,
animalCmgCl: campingItem.animalCmgCl,
tooltip: campingItem.tooltip,
mapX: campingItem.mapX,
mapY: campingItem.mapY,
},
});
} catch (error) {
console.log(error);
}
};
- addDoc() likeList에 추가할 데이터를 적는다.
해당 유저의 데이터만 보여줘야하니까 비교할 수 있게 userId 그리고 들어갈 캠핑 데이터
* 파이어베이스 데이터 삭제
// 좋아요 삭제
export const removeLike = async (docId: string) => {
try {
await deleteDoc(doc(db, "likeList", docId));
} catch (error) {
console.log(error);
}
};
공식 문서에 나와있는 삭제 방법
await deleteDoc(doc(db, "cities", "DC"));
- 아주 간단! deleteDoc(doc(데이터베이스, "컬렉션 이름", 문서id));
- docId를 매개변수로 불러주고 사용할 때 docId를 불러와서 넣어줄 것이다.
* 파이어베이스 데이터 가져오기
// 좋아요 리스트
export const getLikeList = async (userId: string) => {
try {
const q = query(likeListItem, where("userId", "==", userId));
const snapshot = await getDocs(q);
return snapshot.docs.map((doc) => doc.data().campingItem);
} catch (error) {
console.log(error);
return [];
}
};
- query()로그인 한 유저와 데이터베이스의 userId를 비교해서 해당유저의 데이터만 가져온다.
- getDoc() 쿼리의 결과에 일치하는 문서들을 가져온다.
- 해당 문서의 campingItem 데이터를 추출하여 배열로 반환한다.
- 좋아요 한 캠핑장 리스트를 확인하고 리스트 안에서는 좋아요를 취소해도 새로고침 전까지는 유지하고 싶었기때문에 getDocs()를 사용했다.
실시간으로 가져오려면 onSnapshot()을 이용해야한다.
* 파이어베이스 데이터 실시간으로 가져오기
// 좋아요 상태
export const likeState = (
userId: string,
likeUpdate: (likeItems: Array<{ contentId: string; docId: string }>) => void,
): (() => void) | undefined => {
if (!userId) return;
try {
const q = query(likeListItem, where("userId", "==", userId));
// 실시간 조회로 바꿈
const unsubscribe = onSnapshot(q, (snapshot) => {
const updatedLike =
snapshot.docs.map((doc) => ({
contentId: doc.data().campingItem.contentId as string,
docId: doc.id, // Firestore 문서 ID
})) || [];
likeUpdate(updatedLike);
});
return unsubscribe;
} catch (error) {
console.log(error);
}
};
- 좋아요 상태를 확인하는 코드는 실시간으로 업데이트되어야하니까 (하트 누르기) 실시간으로 업데이트를 해줬다.
- 데이터 삭제를 위해 문서 id와 좋아요를 누른 캠핑장을 구분하기 위해 캠핑장의 contentId를 넘겨줬다.
사용하기
export default function LikeBtn({
className,
onClick,
campingItem,
like,
docId,
}: LikeBtnProps) {
const { user } = useAuth();
const onClickLike = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
if (!user) {
onClick(e);
} else {
if (!like) {
void addLike(campingItem, user.uid);
} else {
void removeLike(docId);
}
}
};
- 로그인을 한 상태가 아닐때는 로그인 페이지로 이동시켜야하기 때문에 onClick이벤트를 props로 넘겨줬다.
interface IPropsCampingList {
list: ICampingList[];
className?: string;
onClick: (item: ICampingList) => void;
}
export default function CampingCard({
list,
className,
onClick,
}: IPropsCampingList) {
const [likeList, setLikeList] = useState<
Array<{ contentId: string; docId: string }>
>([]);
const { currentModal, openModal } = useModal();
const router = useRouter();
const { user } = useAuth();
const isMounted = useRef(true);
useEffect(() => {
isMounted.current = true;
if (user) {
// 좋아요 상태 불러오기
const unsubscribe = likeState(user.uid, (updatedLikes) => {
setLikeList(updatedLikes);
});
// 컴포넌트 언마운트 시 구독 해제
return () => {
isMounted.current = false;
if (unsubscribe) {
unsubscribe();
}
};
}
}, []);
const onClickLike = (e: React.MouseEvent<HTMLButtonElement>) => {
if (!user) {
openModal("likeAlert");
}
};
const closeModal = () => {
router.back();
setTimeout(() => {
void router.push("/login");
}, 100);
};
return (
<>
{list.map((item: ICampingList) => {
// 입지 구분 아이콘 리스트
const icons = item.lctCl ? item.lctCl.split(",") : [];
const iconList = icons
.slice(0, 3)
.concat(
Array(3 - icons.length > 0 ? 3 - icons.length : 0).fill("없음"),
);
// 좋아요 리스트에서 contentId가 캠핑장 contentId하고 같으면 문서의 id를 반환
const key =
likeList.find((like) => like.contentId === item.contentId)?.docId ??
item.contentId;
// 좋아요 리스트의 contetnId와 캠핑장 contetnId가 같으면 true 반환
const isLiked = likeList.some(
(like) => like.contentId === item.contentId,
);
return (
<CardWrap
key={key}
className={className}
onClick={() => {
onClick(item);
}}
>
<CardInner>
<ImgBox>
<LikeBtn
className="like"
onClick={onClickLike}
campingItem={item}
like={isLiked}
docId={key}
/>
......
- 좋아요 상태를 불러오는데
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.오류가 나왔다. 찾아보니 컴포넌트가 언마운트된 상태에서 상태 업데이트를 시도할 때 발생한다고 한다.
https://stackoverflow.com/questions/56442582/react-hooks-cant-perform-a-react-state-update-on-an-unmounted-component
React-hooks. Can't perform a React state update on an unmounted component
I get this error: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and
stackoverflow.com
- 해결하는 가장 쉬운 방법은 마운트 되었는지 상태를 추적하는 것.
useRef를 사용해서 isMounted 변수를 선언했고 컴포넌트의 마운트 상태를 추적한다.
++ 내 캠핑장 페이지네이션을 추가해야한다.
+++ 캠핑 후기게시판 만들기...
https://nomadcoders.co/nwitter/lectures/4527
https://stackoverflow.com/questions/56442582/react-hooks-cant-perform-a-react-state-update-on-an-unmounted-component
https://velog.io/@cjw020607/Firebase-Cloud-Firestore-Storage-%EC%95%88%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%82%AD%EC%A0%9C%ED%95%98%EA%B8%B0-deleteDoc-deleteObject
https://velog.io/@phjjj/%ED%8C%8C%EC%9D%B4%EC%96%B4%EB%B2%A0%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0
'언어 > Next.js' 카테고리의 다른 글
[Next 고캠핑] FileReader 이용해 이미지 보여주기 (2) | 2024.09.05 |
---|---|
[Next 고캠핑] FirebaseError: The query requires an index. You can create it here: (0) | 2024.08.24 |
[Next 고캠핑] 로컬 데이터 파이어베이스로 변경하기 (0) | 2024.08.21 |
[Next 고캠핑] 로그인 상태관리, 로그아웃 (2) | 2024.08.14 |
[Next 고캠핑] 파이어베이스 중복 오류, 회원가입 (0) | 2024.08.12 |