ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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 보내면서 포스트 잘 가지고 온다. 

     

     

     

     

     

     

     

     

Designed by Tistory.