React (LikeDislikes.js)
Like, Dislike를 누를 때 발생하는 3가지 경우의 수를 생각해보자.
- 좋아요, 싫어요 둘다 눌리지 않은 상태 -> 둘 중 하나를 누르면 좋아요/싫어요 등록
- 좋아요가 이미 눌려 있는 상태에서 싫어요를 누른다면 -> 좋아요 삭제 후 싫어요 등록
- 싫어요가 이미 눌려 있는 상태에서 좋아요를 누른다면 -> 싫어요 삭제 후 좋아요 등록
과정
- Like, Dislike (좋아요, 싫어요 숫자 개수를 저장)와 LIkeAction, DislikeAction(좋아요, 싫어요가 눌렸는지 여부)를 저장하는 state를 준비한다.
- API 호출을 할 때 정보를 전달할 변수 variables를 준비한다. Like, Dislike를 포스팅과 댓글 모두에 사용할 수 있게 설계했기 때문에 해당 컴포넌트가 어디에 쓰이는지 먼저 검사해야 한다. props에 postId가 존재한다면 variables에 postId 값을 첨부하고, props.comment가 존재한다면 commentId 값을 첨부한다.
- useEffect()에서 /getLikes와 /getDislikes를 가져온다. 그러면 { userId: 값, postId or commentId: 값 } 형태의 배열을 받을 것이다. 그 배열의 길이가 바로 Like/Dislike 개수이고, 그 중에 현재 로그인한 userId와 일치하는 값이 존재한다면 이미 Like/Dislike를 누른 것이므로 LikeAction/DislikeAction을 true로 설정해준다.
- onClick 함수를 Like, Dislike 버튼에 각각 만든다. 버튼을 누르면 먼저 Redux에 들어있는 유저 정보를 검사해서 로그인이 되어있는지부터 확인한다.
- 버튼이 3가지 경우의 수 중 어느 하나에 해당되는지 검사해서 그에 알맞는 동작을 하게 한다. 예를 들어 Like 버튼이 눌린 여부가 null이어도 Dislike가 눌려서 그런 것일 수도 있으므로 검사를 한다든가,
- 이미 어느 한 개가 눌려 있고 다른 쪽을 누르는 경우, 원래 등록된 state를 null로 만들고 DB 정보를 삭제하는 API를 호출하는 것을 잊지 말자
import React, { useEffect, useState } from 'react';
import { Tooltip, Icon } from 'antd';
import axios from 'axios';
import { useSelector } from 'react-redux';
function LikeDislikes(props) {
const user = useSelector(state => state.user);
const [Likes, setLikes] = useState(0);
const [Dislikes, setDislikes] = useState(0);
const [LikeAction, setLikeAction] = useState(null);
const [DislikeAction, setDislikeAction] = useState(null);
let variable = {};
if(props.postId) {
variable = { postId: props. postId, userId: props.userId }
} else {
variable = { commentId: props.commentId , userID: props.userId }
}
useEffect(() => {
axios.post('/api/like/getLikes', variable)
.then(response => {
if(response.data.success) {
setLikes(response.data.likes.length);
response.data.likes.map(like => {
if(like.userId === props.userId) {
setLikeAction(true);
}
})
} else {
alert('Likes 정보를 가져오는데 실패했습니다.')
}
});
axios.post('/api/like/getDislikes', variable)
.then(response => {
if(response.data.success) {
setDislikes(response.data.dislikes.length);
response.data.dislikes.map(like => {
if(like.userId === props.userId) {
setDislikeAction(true);
}
})
} else {
alert('Dislikes 정보를 가져오는데 실패했습니다.')
}
});
}, [])
const onLike = () => {
if(user.userData && !user.userData.isAuth) {
return alert('로그인을 해주세요');
}
if(LikeAction === null) {
axios.post('/api/like/upLike', variable)
.then(response => {
if(response.data.success) {
setLikes(Likes + 1);
setLikeAction(true);
// like가 null이어도 dislike가 이미 눌려있어서 그럴 수도 있기 때문에 dislike 여부도 확인한다.
if(DislikeAction !== null) {
setDislikeAction(null);
setDislikes(Dislikes - 1);
}
} else {
alert('Like를 올리는데 실패했습니다.');
}
});
} else {
axios.post('/api/like/unLike', variable)
.then(response => {
if(response.data.success) {
setLikes(Likes - 1);
setLikeAction(null);
} else {
alert('Like를 취소하는데 실패했습니다.');
}
});
}
};
const onDislike = () => {
if(user.userData && !user.userData.isAuth) {
return alert('로그인을 해주세요');
}
if(DislikeAction === null) {
axios.post('/api/like/upDislike', variable)
.then(response => {
if(response.data.success) {
setDislikes(Dislikes + 1);
setDislikeAction(true);
// dislike가 null이어도 like가 이미 눌려있어서 그럴 수도 있기 때문에 like 여부도 확인한다.
if(LikeAction !== null) {
setLikeAction(null);
setLikes(Likes - 1);
}
} else {
alert('Dislike를 올리는데 실패했습니다.');
}
});
} else {
axios.post('/api/like/unDislike', variable)
.then(response => {
if(response.data.success) {
setDislikes(Dislikes - 1);
setDislikeAction(null);
} else {
alert('Like를 취소하는데 실패했습니다.');
}
});
}
}
return (
<div>
<span key="comment-basic-like" >
<Tooltip title="Like">
<Icon type="like"
theme={LikeAction ? 'filled' : 'outlined'}
onClick={onLike}
/>
</Tooltip>
</span>
<span style={{ padingLeft: '8px', cursor: 'auto' }}> {Likes} </span>
<span key="comment-basic-dislike" >
<Tooltip title="Dislike">
<Icon type="dislike"
theme={DislikeAction ? 'filled' : 'outlined'}
onClick={onDislike}
/>
</Tooltip>
</span>
<span style={{ padingLeft: '8px', cursor: 'auto' }}> {Dislikes} </span>
</div>
)
}
export default LikeDislikes
API
model
Like와 Dislike를 따로 관리해야 하기 때문에 model도 두 개를 만들어준다.
LikeDislikes 컴포넌트를 포스트에서뿐만 아니라 댓글에서도 공통으로 사용할 수 있도록 userId, commentId, postId 세 가지 요소를 갖게 한다. 그러면 find()할 때 포스트는 postId로 찾고, 댓글은 commentId로 찾는 식으로 구별할 수 있다.
Like.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const LikeSchema = mongoose.Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'User'
},
commentId: {
type: Schema.Types.ObjectId,
ref: 'Comment'
},
postId: {
type: Schema.Types.ObjectId,
ref: 'Post'
}
}, { timestamps: true });
const Like = mongoose.model('Like', LikeSchema);
module.exports = { Like };
Dislike.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const DislikeSchema = mongoose.Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'User'
},
commentId: {
type: Schema.Types.ObjectId,
ref: 'Comment'
},
postId: {
type: Schema.Types.ObjectId,
ref: 'Post'
}
}, { timestamps: true });
const Dislike = mongoose.model('Dislike', DislikeSchema);
module.exports = { Dislike };
router (router/like.js)
- 조회 - /getLikes, /getDislikes
- 등록 - /upLike, /upDislike
- 취소 - /unLike, /unDislike
const express = require('express');
const router = express.Router();
const { Like } = require('../models/Like');
const { Dislike } = require('../models/Dislike');
router.post('/getLikes', (req, res) => {
let variable = {};
if(req.body.postId) {
variable = { postId: req.body.postId }
} else {
variable = { commentId: req.body.commentId }
}
Like.find(variable)
.exec((err, likes) => {
if(err) return res.status(400).send(err);
res.status(200).json({ success: true, likes });
})
});
router.post('/getDislikes', (req, res) => {
let variable = {};
if(req.body.postId) {
variable = { postId: req.body.postId }
} else {
variable = { commentId: req.body.commentId }
}
Dislike.find(variable)
.exec((err, dislikes) => {
if(err) return res.status(400).send(err);
res.status(200).json({ success: true, dislikes });
})
});
router.post('/upLike', (req, res) => {
let variable = {};
if(req.body.postId) {
variable = { postId: req.body.postId, userId: req.body.userId }
} else {
variable = { commentId: req.body.commentId, userId: req.body.userId }
}
// 만약 dislike가 이미 클릭되어있다면 like 정보를 저장하면서 dislike 기록 삭제
const like = new Like(variable);
like.save((err, likeResult) => {
if(err) return res.json({ success: false, err })
Dislike.findOneAndDelete(variable)
.exec((err, disLikeResult) => {
if(err) return res.status(400).json({ success: false, err });
res.status(200).json({ success: true });
});
})
});
router.post('/upDislike', (req, res) => {
let variable = {};
if(req.body.postId) {
variable = { postId: req.body.postId, userId: req.body.userId }
} else {
variable = { commentId: req.body.commentId, userId: req.body.userId }
}
const dislike = new Dislike(variable);
dislike.save((err, dislikeResult) => {
if(err) return res.json({ success: false, err })
Like.findOneAndDelete(variable)
.exec((err, likeResult) => {
if(err) return res.status(400).json({ success: false, err });
res.status(200).json({ success: true });
});
})
});
router.post('/unLike', (req, res) => {
let variable = {};
if(req.body.postId) {
variable = { postId: req.body.postId, userId: req.body.userId }
} else {
variable = { commentId: req.body.commentId, userId: req.body.userId }
}
Like.findOneAndDelete(variable)
.exec((err, result) => {
if(err) return res.status(400).json({ success: false, err });
res.status(200).json({ success: true });
});
});
router.post('/unDislike', (req, res) => {
let variable = {};
if(req.body.postId) {
variable = { postId: req.body.postId, userId: req.body.userId }
} else {
variable = { commentId: req.body.commentId, userId: req.body.userId }
}
Dislike.findOneAndDelete(variable)
.exec((err, result) => {
if(err) return res.status(400).json({ success: false, err });
res.status(200).json({ success: true });
});
});
module.exports = router;
'UI 구현 연구일지' 카테고리의 다른 글
[React] Debounce(디바운스) 적용한 검색어 자동완성 구현기 (1) (0) | 2023.06.14 |
---|---|
[React 컴포넌트] 댓글 기능 구현하기 (0) | 2022.07.12 |
[React 컴포넌트] Favorite(찜, 좋아요) 버튼 만드는 법 (0) | 2022.07.11 |
[React 컴포넌트] 더 보기(Load More) 버튼 만드는 법 (0) | 2022.07.08 |
[JS 컴포넌트] 캐러셀 이미지 슬라이드 만들기 (0) | 2022.05.30 |