NODE.JS
Social Media 만들기 - 18) SavePost 구현하기
dodop
2021. 8. 1. 23:09
먼저 아이콘을 눌러서 포스트를 저장하는 기능을 만들자.
user 모델에 다음과 같이 save :post레퍼런스를 만들자.
saved: [{type: mongoose.Types.ObjectId, ref: 'Post'}]
}, {
timestamps: true
})
post Router에 포스트를 저장하고 지우는 라우터를 만들자.
postRouter.patch('/save/:id', auth, async(req, res)=>{
try{
const user = await User.find({_id:req.user.id, saved:req.params.id})
if(user.length>0) return res.status(400).json({msg:"You already saved this post."})
const save = await User.findOneAndUpdate({_id:req.user.id},{
$push:{saved:req.params.id}
}, {new:true})
if(!save) return res.status(400).json({msg:'This post does not exist.'})
res.json({save})
}catch(err){
return res.status(500).json({message:err.message})
}
})
postRouter.patch('/unsave/:id', auth, async(req, res)=>{
try{
const unsave = await User.findOneAndUpdate({_id:req.user.id},{
$pull:{saved:req.params.id}
}, {new:true})
if(!unsave) return res.status(400).json({msg:'This post does not exist.'})
res.json({unsave})
}catch(err){
return res.status(500).json({message:err.message})
}
})
ponst Constants
export const SET_SAVE_POST_REQUEST = 'SET_SAVE_POST_REQUEST'
export const SET_SAVE_POST_SUCCESS = 'SET_SAVE_POST_SUCCESS'
export const SET_SAVE_POST_FAIL='SET_SAVE_POST_FAIL'
export const SET_UNSAVE_POST_REQUEST = 'SET_UNSAVE_POST_REQUEST'
export const SET_UNSAVE_POST_SUCCESS = 'SET_UNSAVE_POST_SUCCESS'
export const SET_UNSAVE_POST_FAIL='SET_UNSAVE_POST_FAIL'
postActions
export const setSavePost=(post) =>async(dispatch, getState)=>{
dispatch({
type:SET_SAVE_POST_REQUEST,
payload:{loading:true}
})
const {userLogin:{userInfo}} = getState();
try{
const res = await axios.patch(`/api/post/save/${post._id}`,null, {
headers:{authorization:`Bearer ${userInfo.token}`}
})
dispatch({
type:SET_SAVE_POST_SUCCESS,
payload:res.data.save
})
dispatch({
type:USER_LOGIN_SUCCESS,
payload:{user:res.data.save, token:userInfo.token}
})
localStorage.removeItem('userInfo')
localStorage.setItem('userInfo',JSON.stringify({user:res.data.save, token:userInfo.token}))
}catch(error){
dispatch({
type:SET_SAVE_POST_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message
})
}
}
export const setUnsavePost=(post) =>async(dispatch, getState)=>{
dispatch({
type:SET_UNSAVE_POST_REQUEST,
payload:{loading:true}
})
const {userLogin:{userInfo}} = getState();
try{
const res = await axios.patch(`/api/post/unsave/${post._id}`,null, {
headers:{authorization:`Bearer ${userInfo.token}`}
})
dispatch({
type:SET_UNSAVE_POST_SUCCESS,
payload:res.data.unsave
})
dispatch({
type:USER_LOGIN_SUCCESS,
payload:{user:res.data.unsave, token:userInfo.token}
})
localStorage.removeItem('userInfo')
localStorage.setItem('userInfo',JSON.stringify({user:res.data.unsave, token:userInfo.token}))
}catch(error){
dispatch({
type:SET_UNSAVE_POST_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message
})
}
}
post Reducer
export const postSaveReducer = (state={}, action)=>{
switch(action.type){
case SET_SAVE_POST_REQUEST:
return {loading:true, success:false}
case SET_SAVE_POST_SUCCESS:
return {loading:false, success:true, savedpost:action.payload}
case SET_SAVE_POST_FAIL:
return {loading:false, success:false, error:action.payload}
default:
return state;
}
}
export const postUnsaveReducer = (state={}, action)=>{
switch(action.type){
case SET_UNSAVE_POST_REQUEST:
return {loading:true, success:false}
case SET_UNSAVE_POST_SUCCESS:
return {loading:false, success:true, unsavedpost:action.payload}
case SET_UNSAVE_POST_FAIL:
return {loading:false, success:false, error:action.payload}
default:
return state;
}
}
store.js
savepost:postSaveReducer,
unsavepost:postUnsaveReducer,
CardFooter.js
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import {Link} from 'react-router-dom'
import Send from '../../images/send.svg'
import { likePost, setSavePost, setUnsavePost, unlikePost } from '../../_actions/postActions'
import LikeButton from '../LIkeButton'
import ShareModal from '../ShareModal'
import {BASE_URL} from '../../utils'
function CardFooter({post}) {
const [isSaved, setIsSaved] = useState(false)
useEffect(() => {
if(userInfo.user.saved.find(id=>id===post._id)){
setIsSaved(true)
}else{
setIsSaved(false)
}
}, [userInfo.user.saved, post._id])
const handleSavePost= () =>{
if(!isSaved){
dispatch(setSavePost(post))
}else{
dispatch(setUnsavePost(post))
}
setIsSaved(!isSaved)
}
return (
<div className="card_footer">
<div className="card_icon_menu">
{
isSaved
? <i className="fas fa-bookmark text-info" onClick={handleSavePost}></i>
: <i className="far fa-bookmark " onClick={handleSavePost}></i>
}
</div>
</div>
)
}
export default CardFooter
잘 들어와있다.
localStorage에 잘 set해주었으므로 새로고침해도 잘 유지된다.
이제 저장한 포스트들을 프로필페이지에서 보이도록 하자.
Profile.js
function Profile(props) {
const [saveTab, setSaveTab] = useState(false)
return (
}
{
<div className="profile_tab">
<button className={saveTab? '': 'active'} onClick={()=>setSaveTab(false)}>Posts</button>
<button className={saveTab? 'active': ''} onClick={()=>setSaveTab(true)}>Saved</button>
</div>
}
{
posts && user&& saveTab
? <SavedPost/>
:
<>
{
posts && posts.length ===0
? <h2 className="text-center text-danger">NO POST</h2>
: <>
<div className="post_thumb">
{
posts?.map(post=>(
<Link key={post._id} to={`/post/${post._id}`}>
<div className="post_thumb_display">
{
post.images[0].config.url.match(/video/i)
? <video controls src={post.images[0].data} alt={post.images[0].data}></video>
: <img src={post.images[0].data} alt={post.images[0].data}></img>
}
<div className="post_thumb_menu">
<i className="far fa-heart">{post.likes.length}</i>
<i className="far fa-comment">{post.comments.length}</i>
</div>
</div>
</Link>
))
}
</div>
</>
}
</>
}
</div>
)
}
export default Profile
저장된 포스트들을 가지고 오는 라우터를 작성해주자.
postRouter.get('/profile/savedposts', auth, async(req, res)=>{
try{
const user = await User.findById({_id:req.user.id})
const limit = req.query.limit ||9
const savedposts = await Post.find({
_id:{$in:user.saved}
}).limit(Number(limit)).sort('-createdAt')
console.log(savedposts)
res.json({savedposts, result:savedposts.length})
}catch(err){
return res.status(500).json({message:err.message})
}
})
SavedPost 컴포넌트를 만들어준다.
import axios from 'axios'
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getSavePost, setSavePost } from '../_actions/postActions'
import { ALERT } from '../_constants/globalConstants'
import { GET_SAVE_POST_UPDATE } from '../_constants/postConstants'
import PostThumb from './PostThumb'
import LoadIcon from '../images/loading.gif'
function SavedPost() {
const [savedPosts, setSavedPosts] = useState([])
const [result, setResult] = useState(9)
const [page, setPage] = useState(2)
const [load, setLoad] = useState(false)
const userLogin = useSelector(state => state.userLogin)
const {userInfo} = userLogin
const dispatch = useDispatch()
useEffect(() => {
setLoad(true)
axios.get('/api/post/profile/savedposts', {
headers:{authorization:`Bearer ${userInfo.token}`},
}).then(response=>{
setSavedPosts(response.data.savedposts)
setResult(response.data.result)
setLoad(false)
})
.catch(err=>{
dispatch({
type:ALERT,
payload:{error:err.data.message}
})
})
return ()=>setSavedPosts([])
}, [userInfo.token, dispatch])
const handleLoadMore = async() =>{
setLoad(true)
const res = await axios.get(`/api/post/profile/savedposts?limit=${page*9}`,{
headers:{authorization:`Bearer ${userInfo.token}`},
})
setSavedPosts(res.data.savedposts)
setResult(res.data.result)
setPage(page+1)
setLoad(false)
}
return (
<div>
{
load
? <img src={LoadIcon} alt="loading" className="d-block mx-auto" />
:
<>
<PostThumb posts={savedPosts} result={result}/>
{
result < 9*(page-1)
? ''
:<button className="btn btn-dark mx-auto d-block" onClick={handleLoadMore}>LoadMore</button>
}
</>
}
</div>
)
}
export default SavedPost
리덕스 스토어에 저장하지 않고,
그대로 axios로 보낼것이다.
save 탭누르면 저장된 포스트들 나오고,
loadmore 버튼 누르면 axios 보내면서 포스트 잘 가지고 온다.