React, Next, Redux/⚛ React.JS
좋아요/싫어요 구현하기
DarrenKwonDev
2020. 9. 17. 17:04
로직은 다음과 같습니다.
좋아요(싫어요)가 안 눌려져 있을 때 좋아요를 누르기
=> 좋아요(싫어요) 1 올리기
좋아요(싫어요)가 이미 클릭되어 있는데 다시 누르기
=> 좋아요(싫어요) 1 내리기 (취소)
싫어요(좋아요) 버튼을 클릭한다면
=> 좋아요(싫어요) 1 내리고 싫어요(좋아요) 1 올리기
이번 프로젝트의 의의에는 싫어요 버튼을 제거하는 게 맞다고 생각하여서 싫어요를 반영하는 로직은 삭제했지만 작성한 코드는 아래에 따로 첨부했습니다.
우선 Like 스키마입니다.
좋아요에 해당하는 숫자를 계산하기보다 도큐먼트를 늘리는 방식을 사용하고, 이후 숫자가 필요할 때 조건을 걸어 쿼리를 날리고 length를 계산하도록 합시다.
const mongoose = require("mongoose");
const likeSchema = mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
commentId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Comment",
},
PostId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Post",
},
});
// 인덱스 설정
likeSchema.index({ commentId: 1, userId: 1 });
// 모델의 이름과 스키마를 이용해 모델의 정의함.
const Like = mongoose.model("Like", likeSchema);
module.exports = Like;
antd를 사용중이어서 다음과 같이 작성했습니다.
function SingleComment({ post_id, comment, userData, Comments, setComments }) {
.. 중략
const actions = [
<LikeDislike comment={comment} userData={userData} />,
<span key="comment-basic-reply-to" onClick={onReplyClick}>
Reply to
</span>,
];
return (
<>
<Comment
key={comment._id}
actions={actions}
author={<a>{comment.writer_nickname}</a>}
avatar={
<Avatar src={comment.writer_image} alt={comment.writer_nickname} />
}
content={<ContentsWrapper>{comment.contents}</ContentsWrapper>}
datetime={<DateRefining dateString={comment.createdAt} />}
/>
... 중략
좋아요/싫어요 버튼을 렌더하는 컴포넌트입니다.
likes state는 좋아요의 갯수를 의미하고
action state는 현재 좋아요 버튼을 누른 상태인지, 아무것도 안 누른 상태인지, 싫어요 버튼을 누른 상태인지 등을 저장하는 state입니다.
function LikeDislike({ comment, userData }) {
const [likes, setLikes] = useState(0);
const [action, setAction] = useState(null);
const onLikeClick = () => {
const body = {
commentId: comment._id,
userId: userData._id,
};
// 아무것도 누르지 않은 상태일 때
if (action === null) {
axios.post("/api/like/uplike", body).then((res) => {
if (res.data.upLike) {
setAction("liked");
setLikes((prev) => prev + 1);
} else {
alert(res.data.err);
}
});
} else if (action === "liked") {
// 이미 좋아요를 눌렀는 데 다시 눌렀을 때이므로 취소 로직
axios.post("/api/like/unlike", body).then((res) => {
if (res.data.unLike) {
setAction(null);
setLikes((prev) => prev - 1);
} else {
alert(res.data.err);
}
});
}
};
// 맨 처음 로딩할 때 해당 comment가 몇 개의 좋아요를 눌렀는지
// 로그인한 유저각 이미 좋아요를 눌렀는지를 판단합니다.
useEffect(() => {
const body = {
commentId: comment._id,
};
axios.post("/api/like/getLike", body).then((res) => {
if (res.data.getLike) {
// 얼마나 많은 좋아요를 받았는가
setLikes(res.data.likes.length);
// 내가 이미 좋아요를 눌렀는가
res.data.likes.map((like) => {
if (like.userId === userData._id) {
setAction("liked");
}
});
} else {
alert("좋아요 데이터를 가져오는데 실패했습니다.");
}
});
}, []);
return (
<div style={{ display: "flex" }}>
<div
key="comment-basic-like"
title="Like"
style={{ marginRight: "16px", cursor: "pointer" }}
>
<span onClick={onLikeClick}>
// action이 like면 색을 채운 아이콘 아니면 색이 빈 아이콘을 넣습니다.
{createElement(action === "liked" ? LikeFilled : LikeOutlined)}
<span className="comment-action" style={{ marginLeft: "8px" }}>
// likes 도큐먼트의 갯수(lenght)를 넣습니다.
{likes}
</span>
</span>
</div>
</div>
);
}
export default LikeDislike;
node로 백엔드 로직을 짜게 된다면 다음과 같이 되겠군요.
// ================================================================
// 좋아요를 눌렀을 때
// ================================================================
apiRouter.post("/like/uplike", (req, res) => {
let { commentId, userId } = req.body;
const LikeIns = new Like({ userId, commentId });
LikeIns.save((err, result) => {
if (err) return res.status(400).json({ upLike: false, err });
return res.status(200).json({ upLike: true });
});
});
// ================================================================
// 좋아요를 다시 눌러 취소할 때
// ================================================================
apiRouter.post("/like/unlike", (req, res) => {
let { commentId, userId } = req.body;
console.log(commentId, userId);
Like.findOneAndDelete({ commentId: commentId, userId: userId }).exec(
(err, result) => {
if (err) return res.status(400).json({ unLike: false, err });
return res.status(200).json({ unLike: true });
}
);
});
만약 싫어요도 같이 한다면?
조언하건데, action state하나로 하지 마시고 반드시 여러 개의 action을 두세요. 이게 훨씬 편합니다.
function LikeDislikes(props) {
const [Likes, setLikes] = useState(0)
const [Dislikes, setDislikes] = useState(0)
const [LikeAction, setLikeAction] = useState(null)
const [DislikeAction, setDislikeAction] = useState(null)
... 중략
좋아요, 싫어요를 가져올 때도 두 번을 날려야합니다.
useEffect(() => {
const body = {
commentId: comment._id,
};
axios.post("/api/like/getLike", body).then((res) => {
if (res.data.getLike) {
// 얼마나 많은 좋아요를 받았는가
setLikes(res.data.likes.length);
// 내가 이미 좋아요를 눌렀는가
res.data.likes.map((like) => {
if (like.userId === userData._id) {
setAction("liked");
}
});
} else {
alert("좋아요 데이터를 가져오는데 실패했습니다.");
}
});
axios.post("/api/like/getDislike", body).then((res) => {
if (res.data.getDisLike) {
// 얼마나 많은 싫어요를 받았는가
setLikes(res.data.dislikes.length);
// 내가 이미 싫어요를 눌렀는가
res.data.dislikes.map((dislike) => {
if (dislike.userId === userData._id) {
setAction("disliked");
}
});
} else {
alert("싫어요 데이터를 가져오는데 실패했습니다.");
}
});
}, []);
좋아요를 누를 때는 이미 싫어요를 누른 상태라면 싫어요를 지워줘야 합니다.
역으로, 싫어요를 누를 때 이미 좋아요를 누른 상태라면 좋아요도 지워줘야겠죠.
apiRouter.post("/like/uplike", (req, res) => {
let { commentId, userId } = req.body;
const LikeIns = new Like({ userId, commentId });
LikeIns.save((err, result) => {
if (err) return res.status(400).json({ upLike: false, err });
// 만약 이미 싫어요를 누른 상태라면, 싫어요를 취소야 합니다.
Dislike.findByIdAndDelete(result._id).exec((err, dislikeResult) => {
if (err) return res.status(400).json({ upLike: false, err });
return res.status(200).json({ upLike: true });
});
});
});