-
Social Media 만들기 - 18) SavePost 구현하기NODE.JS 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 보내면서 포스트 잘 가지고 온다.
'NODE.JS' 카테고리의 다른 글
(mongoDB) findOneAndUpdate가 update된 항목을 return 하지 않을 때 (0) 2021.08.05 Social Media 만들기 - 20) socket io 사용(notify 생성하고 전달하기) (0) 2021.08.04 Social Media 만들기 - 17) share, suggestion 구현하기 (0) 2021.07.31 Social Media 만들기 - 16) Discover - loadmore 버튼 구현하기 (2) 2021.07.30 Social Media 만들기 - 16) profile post, detail post 구현하기 (0) 2021.07.19