강의

멘토링

커뮤니티

Inflearn Community Q&A

No author

This post's author information has been deleted.

MERN STACK Community: React from Start to Deployment

Source Code

MainPage 질문

Written on

·

174

1

우선 좋은 영상 감사합니다.  의미있는 경험이 되었던 것 같아요. 거의 다 만들었는데 정말 최종적으로 MainPage안에서 2가지 문제가 생겼습니다.

 

1.   마지막 게시물까지 확인해도 더보기가 사라지지 않습니다.

 

2.  새로고침을 하면 로그아웃은 되지 않으나 순간적으로 token을 읽지 못하고 useEffect에 의해 로그인 페이지로 이동하는 것 같습니다.

 

MainPage.js

import React, { useState, useEffect } from 'react'
import axios from 'axios'
import List from './Post/List'
import '../Style/MainPage.css'
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";

function MainPage() {
    const [sort, setSort] = useState("최신순")
    const [postList, setPostList] = useState([])
    const [searchTerm, setSearchTerm] = useState("")
    const [skip, setSkip] = useState(0)
    const [loadMore, setLoadMore] = useState(true)
   
    const navigate = useNavigate()
    const user = useSelector(state=>state.user)
   
    useEffect(()=>{
        console.log(user.accessToken)
        if(!user.accessToken){
            alert("로그인된 회원만 이용할 수 있습니다.")
            navigate('/login')
        }
        console.log(user.accessToken)
    }, [])

    const getLoadMore = () => {
        let body = {
            sort: sort,
            searchTerm: searchTerm,
            skip: skip
        }
        axios.post('/api/post/list', body)
            .then(res => {
                if (res.data.success) {
                    setPostList([...postList, ...res.data.postList])
                    setSkip(skip + res.data.postList.length)
                    if (res.data.postList.lengh < 3) {
                        setLoadMore(false)
                    }
                }
            }).catch(err => {
                console.log(err)
            })
    }
    const getPostList = () => {
        setSkip(0)
        let body = {
            sort: sort,
            searchTerm: searchTerm,
            skip: 0
        }
        axios.post('/api/post/list', body)
            .then(res => {
                setPostList([...res.data.postList])
                setSkip(res.data.postList.length)
                if (res.data.postList.lengh < 3) {
                    setLoadMore(false)
                    console.log(loadMore)
                }
            }).catch(err => {
                console.log(err)
            })
    }
    useEffect(() => {
        getPostList()
    }, [sort])
    const Search = (e) => {
        getPostList()
    }

    return (
        <div>
            <div className='SearchAndArrange'>
                <button onClick={() => setSort("최신순")}>최신순</button>
                <button style={{ marginLeft: "5px", marginRight: "20px" }} onClick={() => setSort("인기순")}>인기순</button>
                <input style={{ marginRight: "5px" }} placeholder='제목 또는 내용' type="text" value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />
                <button onClick={(e) => Search(e)}>검색</button>
                <button style={{marginLeft: "10px"}} onClick={()=>navigate('/upload')}>게시글 작성</button>
            </div>

            <List postList={postList} />
            {loadMore && (<button className='SeeMore' onClick={() => getLoadMore()}>더 보기</button>)}
        </div>
    )
}

export default MainPage

 

서버쪽 post.js

const express = require('express')
const router = express.Router()
const multer = require('multer')

const { Post } = require('../models/post.js')
const { Counter } = require('../models/counter.js')
const { User } = require("../models/user")
const { Router } = require('express')

router.post('/submit', (req, res) => {       //게시글 작성 기능
    let temp = {
        title: req.body.title,
        content: req.body.content,
        image: req.body.image,
    }
    Counter.findOne({ name: 'counter' }).exec().then(counter => {
        temp.postNum = counter.postNum //counter의 게시물 번호를 지정해 줌.
        User.findOne({ uid: req.body.uid }).exec().then(userInfo => {  //요청된 uid와 일치하는 user를 찾아서
            temp.author = userInfo._id    // 해당 user의 Id번호를 권한번호로 지정한 뒤
            const CommunityPost = new Post(temp) //새로운 포스트모델을 생성하여 요청된 정보와 함께 넘겨줌.
            CommunityPost.save().then(() => {
                Counter.updateOne({ name: "counter" }, { $inc: { postNum: 1 } }) //counter의 게시물 번호를 1증가
                    .then(() => {
                        res.status(200).json({ success: true })
                    })
            })
        })
    }).catch(err => {
        err.status(400).json({ success: false })
    })
})

router.post('/list', (req, res) => {
    let sort = {}
    if(req.body.sort ==="최신순"){
        sort.createdAt = -1
    }else{
        sort.repleNum = -1
    }

    Post.find({$or : [{title: {$regex: req.body.searchTerm}}, {content: {$regex: req.body.searchTerm}}]})
    .populate("author")
    .sort(sort)
    .skip(req.body.skip) //
    .limit(3)
    .exec()
    .then(doc => { //populate는 주어진 정보를 통해 다른 document의 객체를 불러옴. 여기서는 author를 통해 user를 참조하여 Id를 가지고 옴.
        res.status(200).json({ success: true, postList: doc })
    }).catch(err => {
        res.status(400).json({ success: false })
    })
})

router.post('/detail', (req, res) => {
    Post.findOne({ postNum: Number(req.body.postNum) }).populate("author").exec() //요청된 게시물 번호가 일치하는 post를 찾음
        .then(doc => {
            res.status(200).json({ success: true, post: doc }) //post라는 이름으로 해당 post 정보를 전송함.
        }).catch(err => {
            res.status(400).json({ success: false })
        })
})

router.post('/edit', (req, res) => {
    console.log(req.body)
    let temp = {
        title: req.body.title,
        content: req.body.content,
    }
    Post.updateOne({ postNum: Number(req.body.postNum) }, { $set: temp }).exec()  //게시물번호를 넘겨받아 해당 게시물을 temp로 업데이트
        .then(() => {
            console.log("수정성공")
            res.status(200).json({ success: true })
        }).catch(err => {
            console.log("수정실패")
            err.status(400).json({ success: false })
        })
})

router.post('/delete', (req, res) => {
    Post.deleteOne({ postNum: Number(req.body.postNum) }).exec() //전달받은 게시물 번호를 찾아서 삭제해 줌.
        .then(doc => {
            res.status(200).json({ success: true })
        }).catch(err => {
            console.log(err)
            res.status(400).json({ success: false })
        })
})

const storage = multer.diskStorage({ //multer를 통해 전달받은 데이터를 파일 디스크에 저장
    destination: (req, file, cb) => {  //저장할 경로를 지정
        cb(null, 'image')
    },
    filename: (req, file, cb) => { //저장할 파일의 이름 지정
        cb(null, Date.now() + '-' + file.originalname)
    }
})

const upload = multer({ storage: storage }).single('image')

router.post('/image/upload', (req, res) => {
    upload(req, res, (err) => {

        if (err) {
            res.status(400).json({ success: false })
        }
        else {
            res.status(200).json({ success: true, filepath: req.file.path })
        }
    })
})

module.exports = router

 

mongodbexpressfirebasereactnodejs

Answer 1

0

pandacoding님의 프로필 이미지
pandacoding
Instructor

더보기 버튼과 관련해서는 전에 질문을 한 번 주셨죠?

skip이 제대로 변경되는지 확인해봐야 할 것 같습니다.

MainPage에서 user의 유효성 검사를 도입하려는 시도를 고민해본적은 없네요.

하지만 말씀해주신 것처럼 useSelector가 동작하는 도중에 useEffect가 동작할 것 같긴 합니다.

 

해결 방법으로는 다양하게 있습니다.

MainPage를 import 하는 다른 컴포넌트를 만들어서 user를 props의 형태로 주던가,

혹은 이와 비슷하게 제 강의에서는 다루지 않았지만 고차컴포넌트를 만들어서 사용하는 방법도 있습니다.

간단하게 아래와 같이 조건을 바꿔도 좋습니다.

  useEffect(()=>{
    if(user.isLoading && !user.accessToken){
        alert("로그인된 회원만 이용할 수 있습니다.")
        navigate('/login')
    }
    console.log("user : ", user);
  }, [user])

 

* 위 코드를 사용시 Main 컴포넌트가 unmount가 된 이후에 다른 useEffect에 의해 state들의 값이 변경되는 문제가 발생할 수 있습니다. cleanup Func을 부여해주는 방법으로 이를 해결할 수 있습니다.

단, 이번 실습은 MainPage에서 유저의 유효성 검사를 하겠다고 고려된 구조가 아닙니다.

이렇게 구조를 변경하시려면, header의 부분도 조금 바뀌어야 할 것 같은데 고민해보시길 바랍니다. 

 

마지막 방법으로 새로고침 시 redux data를 유지시켜주는 라이브러리를 사용하는 방법이 있습니다.

redux-persist 로 검색하시면 정보를 확인하실 수 있습니다.

No author

This post's author information has been deleted.

Ask a question