매일 땡기는 마라 코딩

[인프런] 유튜브 사이트 만들기 (2) 본문

클론코딩

[인프런] 유튜브 사이트 만들기 (2)

cmkoi1 2023. 1. 13. 19:38

따라하며 배우는 노드, 리액트 시리즈 - 유튜브 사이트 만들기 강의

 

[무료] 따라하며 배우는 노드, 리액트 시리즈  - 유튜브 사이트 만들기 - 인프런 | 강의

이 강의를 통해 리액트와 노드의 개념을 익히는 것뿐만이 아닌 실질적으로 어떻게 웹사이트를 만들 수 있는지를 배울 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

※ 해당 링크 강의 내용을 바탕으로 작성된 포스팅입니다.

 

 

 

디테일 비디오 페이지에 Side 비디오 생성

1. Side Video 부분 Layout template 만들기

  • boilerplate-mern-stack-master\client\src\components\views\VideoDetailPage 경로에 Sections 폴더 생성.
  • boilerplate-mern-stack-master\client\src\components\views\VideoDetailPage\Sections 경로에 SideVideo.js 파일 생성 및 코드 작성.

SideVideo.js

import React from 'react'

function SideVideo() {
  return (
    <div>
        SideVideo
    </div>
  )
}

export default SideVideo

VideoDetailPage.js

//import Axios from "axios";
import SideVideo from './Sections/SideVideo'; //SideVideo.js를 연결
                <Col lg={6} xs={24}>
                    <SideVideo />
                </Col>



2. 한 개의 카드 template 만들기

SideVideo.js

    return (
        <div style={{ display: 'flex', marginBottom: "1rem", padding: '0 2rm' }}>
            <div style={{ width: '40%', marginBottom: '1rem' }}>
                <a href>
                    <img style={{ width: '100%' }} src alt />
                </a>
            </div>

            <div style={{ width: '50%' }}>
                <a href>
                    <span style={{ fontSize: '1rem', color: 'black' }}>videoTitle</span><br />
                    <span>videoWriterName</span><br />
                    <span>videoViews</span><br />
                    <span>Time</span>
                </a>
            </div>

        </div>
    )



3. DB에서 모든 비디오 데이터 불러오기

SideVideo.js

import React, { useEffect, useState } from 'react'
import Axios from 'axios';

function SideVideo() {

    const [sideVideos, setsideVideos] = useState([])

    useEffect(() => {
    //랜딩 페이지에서 불러온 비디오 데이터 api 사용     
        Axios.get('/api/video/getVideos')
        .then(response => {
            if(response.data.success) {
                console.log(response.data)
                setsideVideos(response.data.videos)
            } else {
                alert('비디오 가져오기를 실패했습니다.')
            }
        })

    }, [])



4. 불러온 데이터 화면에 출력

SideVideo.js

    const renderSideVideo = sideVideos.map((video, index) => {
       
        var minutes = Math.floor(video.duration / 68);
        var seconds = Math.floor((video.duration - minutes * 60));

        return <div key={index} style={{ display: 'flex', marginBottom: "1rem", padding: '0 2rm' }}>
        <div style={{ width: '40%', marginRight: '1rem' }}>
            <a href>
                <img style={{ width: '100%', height: '100%' }} src={`http://localhost:5000/${video.thumbnail}`} alt="thumbnail" />
            </a>
        </div>

        <div style={{ width: '50%' }}>
            <a href style={{ color: 'gray' }}>
                <span style={{ fontSize: '1rem', color: 'black' }}>{video.title}</span><br />
                <span>{video.writer.name}</span><br />
                <span>{video.views} views </span><br />
                <span>{minutes} : {seconds}</span>
            </a>
        </div>

    </div>


    })

    return (

        <React.Fragment>
            <div style={{ marginTop: '3rem' }} />

            {renderSideVideo}
        </React.Fragment>


    
    )

 

 

 

구독 기능 (1)

1. Subscriber Model 만들기

필요한 데이터

  • boilerplate-mern-stack-master\server\models 경로에 Subscriber.js 파일 생성하고 Video.js 코드 복사 및 수정.

Subscriber.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const subscriberSchema = mongoose.Schema({

    userTo: {
        type: Schema.types.ObjectId,
        ref:'User'
    },
    userFrom: {
        type: Schema.types.ObjectId,
        ref:'User'
    }

}, { timestamps: true }) //만든날과 업데이트날 표시


const Subscriber = mongoose.model('Subscriber', subscriberSchema);

module.exports = { Subscriber }



2. Subscribe Button UI 만들기

  • boilerplate-mern-stack-master\client\src\components\views\VideoDetailPage\Sections경로에 Subscribe.js 파일 생성.

Subscribe.js

import React from 'react'

function Subscribe() {
    return (
        <div>
            <button
                style={{
                    backgroundColor: '#CC0000', borderRadius: '4px',
                    color: 'white', padding: '10px 16px',
                    fontWeight: '500', fontSize: '1rem', textTransform: 'uppercase'
                }}
                onClicks
            >
                0 Subscribe
            </button>
        </div>
    )
}

export default Subscribe

VideoDetailPage.js

//import SideVideo from './Sections/SideVideo'; //SideVideo.js를 연결
import Subscribe from './Sections/Subscribe'; //Subscribe.js를 연결
                        <List.Item
                            actions={[<Subscribe/>]}
                        >


3. 데이터베이스에서 유저 구독자 수 정보 가져오기

VideoDetailPage.js

//    if(VideoDetail.writer) { //이미지가 로딩 전에 렌더링되어 나는 오류를 방지
         // userFrom과 userTo이 다르면 버튼이 안 나오도록(내가 작성자거나 비회원)
        const subcribeButton = VideoDetail.writer._id !== localStorage.getItem('userId') && <Subscribe userTo={VideoDetail.writer._id} userFrom={localStorage.getItem('userId')} />
                        <List.Item
                            actions={[ subcribeButton ]}
                        >

Subscribe.js

import Axios from 'axios'
import React, { useEffect, useState } from 'react'

function Subscribe(props) {

    const [SubscribeNumber, setSubscribeNumber] = useState(0)

    useEffect(() => {

        let variable = { userTo: props.userTo } //몇 명의 구독자가 있는지
        
        Axios.post('/api/subscribe/subscribeNumber', variable)
            .then(response => {
                if(response.data.success) {
                    setSubscribeNumber(response.data.subscribeNumber)
                } else {
                    alert('구독자 수 정보를 받아오지 못했습니다.')
                }
            })

    }, [])
  • boilerplate-mern-stack-master\server\routes 경로에 subscribe.js 파일 생성, Video 코드 복사 및 수정.

subscribe.js

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

const { Subscriber } = require("../models/Subscriber");

//=================================
//             Subscribe
//=================================


router.post('/subscribeNumber', (req, res) => {

    Subscriber.find({ 'userTo': req.body.userTo })
    .exec((err, subscribe) => { //userTo를 구독하는 모든 case가 들어있음. 3명이 구독하면 3개의 case
        if(err) return res.status(400).send(err);
        return res.status(200).json({ success: true, subscribeNumber: subscribe.length })
    })

});


module.exports = router;

index.js

//app.use('/api/video', require('./routes/video'));
app.use('/api/subscribe', require('./routes/subscribe'));



4. 내가 구독하는 유저 정보 가져오기

  • 로그인할 때 유저 Id만 임의적으로 localStorage에 넣어 주고 어느 곳에서나 쓸 수 있게 해 둠.

Subscribe.js

//    const [subscribeNumber, setSubscribeNumber] = useState(0)
    const [Subscribed, setSubscribed] = useState(false)
        let subscribedVariable = { userTo: props.userTo, userFrom: localStorage.getItem('userId') }

        Axios.post('/api/subscribe/subscribed', subscribedVariable)
            .then(response => {
                if(response.data.success) {
                    setSubscribed(response.data.subscribed)
                } else {
                    alert('정보를 받아오지 못했습니다.')
                }
            })

//    }, [])

subscribe.js

router.post('/subscribed', (req, res) => {

    Subscriber.find({'userTo': req.body.userTo, 'userFrom': req.body.userFrom })
    .exec((err, subscribe) => { //length가 1이면 구독을 하고 있다는 것. 0이면 구독 x
        if(err) return res.status(400).send(err);
        let result = false //구독 x
        if(subscribe.length !== 0) { //length가 0이 아니라면
            result = true //구독 중
        }
        res.status(200).json({ success: true, subscribed: result })
    })

});


//module.exports = router;




5. 가져온 정보 화면에 출력

subscribe.js

            <button
                style={{
                    backgroundColor: `${ Subscribed ? '#AAAAAA' : '#CC0000' }`, borderRadius: '4px',
                    color: 'white', padding: '10px 16px',
                    fontWeight: '500', fontSize: '1rem', textTransform: 'uppercase'
                }}
                onClicks
            >
                {SubscribeNumber} {Subscribed ? 'Subscribed': 'Subscribe'}
            </button>

 

오류 나서 절망했는데
오타났네 바보
여기 this도 지워 줘야 함
이거 하나 만들라고 고생을...!

 

 

 

구독 기능 (2)

  • 먼저, 필요한 데이터들을 props를 통해 불러와준다.

Subscribe.js

//    }, [])

    const onSubscribe = () => {

        let subscribedVariable = { //자신과 업로더의 id가 필요

            userTo: props.userTo,
            userFrom: props.userFrom

        }

        if(Subscribed) { //이미 구독중이면

        } else { //구독 중이 아니면

        }

    }
                onClick={onSubscribe}

VideoDetailPage.js

                            actions={[<Subscribe userTo={VideoDetail.writer._id} userFrom={localStorage.getItem('userId')} />]}



1. 구독하기 기능 만들기

Subscribe.js

} else { //구독 중이 아니면

            Axios.post('/api/subscribe/subscribe', subscribedVariable)
                .then(response => {
                    if(response.data.success) {
                        setSubscribeNumber(SubscribeNumber + 1)
                        setSubscribed(!Subscribed)
                    } else {
                        alert('구독하는데 실패했습니다.')
                    }
                })

        }

subscribe.js

router.post('/subscribe', (req, res) => {
    //구독이므로 DB에 userTo와 userFrom을 저장
    const subscribe = new Subscriber(req.body)

    subscribe.save((err, doc) => {
        if(err) return res.json({ success: false, err })
        res.status(200).json({ success: true })
    })

});

 

2. 구독 취소하기 기능 만들기

Subscribe.js

if(Subscribed) { //이미 구독중이면
            
            Axios.post('/api/subscribe/unSubscribe', subscribedVariable)
                .then(response => {
                    if(response.data.success) {
                        setSubscribeNumber(SubscribeNumber - 1) //현재 구독자 수 -1
                        setSubscribed(!Subscribed) //현 상태와 반대
                    } else {
                        alert('구독 취소하는데 실패했습니다.')
                    }
                })

subscribe.js

router.post('/unSubscribe', (req, res) => {
    //구독 취소이므로 userTo와 userFrom을 찾아 없애줌
    Subscriber.findOneAndDelete({ userTo: req.body.userTo, userFrom: req.body.userFrom })
        .exec((err, doc) => {
            if(err) return res.status(400).json({ success: false, err })
            res.status(200).json({ success: true, doc })
        })

});

버튼 안 눌리는 오류 나서 눈 빠지게 코드 확인...
이눔자식... subscribeNumber을 subscribed로 바꿔야 하는데 잊어먹음
이번엔 구독 무한 제공 사건 발생

수많은 오타 찾아서 해결...

구독 취소가 안 됨...

subscribe 라우터 구독 취소 부분에 findOneAndDelete 명령어를 ()가 아닌 []로 사용하고 있었음. 해결, DB에서 잘 삭제되는 것도 확인.

 

 

 

구독 비디오 페이지

1. 빈 Subscription 페이지 생성

  • boilerplate-mern-stack-master\client\src\components\views 경로에 SubscriptionPage 폴더 생성.
  • boilerplate-mern-stack-master\client\src\components\views\SubscriptionPage 경로에 SubscriptionPage.js 파일 생성.

SubscriptionPage.js

import React from 'react'

function SubscriptionPage() {
  return (
    <div>
        SubscriptionPage
    </div>
  )
}

export default SubscriptionPage
  • 페이지를 메뉴에 올려 주기 위해 LeftMenu.js 코드를 수정한다.

LeftMenu.js

import React from 'react';
import { Menu } from 'antd';
const SubMenu = Menu.SubMenu;
const MenuItemGroup = Menu.ItemGroup;

function LeftMenu(props) {
  return (
    <Menu mode={props.mode}>
    <Menu.Item key="mail">
      <a href="/">Home</a>
    </Menu.Item>
    <Menu.Item key="subscription">
      <a href="/subscription">Subscription</a>
    </Menu.Item>
  </Menu>
  )
}

export default LeftMenu



2. Subscription Page를 위한 Route 만들기

App.js

//import VideoDetailPage from "./views/VideoDetailPage/VideoDetailPage"
import SubscriptionPage from './views/SubscriptionPage/SubscriptionPage';
//          <Route exact path="/video/:videoId" component={Auth(VideoDetailPage, null)} />
          <Route exact path="/subscription" component={Auth(SubscriptionPage, null)} />



3. Template 만들기

  • 비슷한 양식인 LandingPage.js의 코드를 SubscriptionPage.js에 복사해 온다.

SubscriptionPage.js

import React, { useEffect, useState } from 'react'
import { FaCode } from "react-icons/fa";
import { Card, Icon, Avatar, Col, Typography, Row } from 'antd';
import Axios from 'axios';
import moment from 'moment';
const { Title } = Typography
const { Meta } = Card;

function SubscriptionPage() {

    const [Video, setVideo] = useState([])

    useEffect(() => { //페이지가 로드되면 실행, input이 없으면 계속 실행
        Axios.get('/api/video/getSubscriptionVideos')
        .then(response => {
            if(response.data.success) {
                console.log(response.data)
                setVideo(response.data.videos)
            } else {
                alert('비디오 가져오기를 실패했습니다.')
            }
        })

    }, [])//[]가 있으면 한 번만 실행

    const renderCards = Video.map((video, index) => {

        var minutes = Math.floor(video.duration / 68);
        var seconds = Math.floor((video.duration - minutes * 60));

        return <Col lg={6} md={8} xs={24}>
            <div style={{ position: 'relative'}}>
                <a href={`/video/${video._id}`} >
                    <img style={{ width: '100%' }} src={`http://localhost:5000/${video.thumbnail}`} alt="thumbnail" />
                    <div className='duration'>
                        <span>{minutes} : {seconds}</span>
                    </div>
                </a>
            </div><br />
            <Meta
                avatar={
                    <Avatar src={video.writer.image} />
                }
                title={video.title}
            />
            <span>{video.writer.name}</span><br />
            <span style={{ marginLeft: '3rem' }}>{video.views} views</span> - <span>{moment(video.createdAt).format("MMM Do YY")}</span>
        </Col>
    })

    return (
        <div style={{ width: '85%', margin: '3rem auto' }}>
            <Title level={2}> Recommended </Title>
            <hr />
            <Row gutter={[32, 16]}> 


                {renderCards}
                

            </Row>
        </div>
    )
}

export default SubscriptionPage



4. 내가 구독한 유저의 비디오들만 서버에서 가져오고 비디오들을 화면에 출력

  • LandingPage.js의 코드를 가져왔기 때문에 출력 부분은 따로 수정해 주지 않아도 된다.

video.js

const { Subscriber } = require("../models/Subscriber");
router.post('/getSubscriptionVideos', (req, res) => {

    // 자신의 아이디를 가지고 구독하는 사람들을 찾는다.
    Subscriber.find({ userFrom: req.body.userFrom })
        .exec((err, subscriberInfo) => { //subscriberInfo는 userFrom이 userTo를 구독하고 있는 정보
            if(err) return res.status(400).send(err);

            let subscribedUser = [];
            // subscribedUser 변수 안에 userTo 정보를 넣는 과정
            subscriberInfo.map((subscriber, i) => {
                subscribedUser.push(subscriber.userTo);
            })


            // 찾은 사람들의 비디오를 가지고 온다.
            Video.find({ writer: { $in: subscribedUser } })
            //writer: req.body.id 명령어 불가능, subscribedUser가 여러 명일 수 있기 때문.
            //$in을 이용하면 몇 명이든 subscribedUser에 있는 id를 가지고 writer를 찾을 수 있다.
                .populate('writer')
                .exec((err, videos) => {
                    if(err) return res.status(400).send(err);
                    res.status(200).json({ success: true, videos })
                })
        })

});

SubscriptionPage.js

useEffect(() => {
        
        const subscriptionVariables = { //본인의 구독 실황을 찾기 위해 본인 id 필요
            userFrom: localStorage.getItem('userId')
        }

        Axios.post('/api/video/getSubscriptionVideos', subscriptionVariables)
        .then(response => {
            if(response.data.success) {
                console.log(response.data)
                setVideo(response.data.videos)
            } else {
                alert('비디오 가져오기를 실패했습니다.')
            }
        })

    }, [])

 

 

 

 

댓글 기능 생성 (1) 구조 설명

1. 댓글 부분 구조 설명

DETAIL VIDEO PAGE가 아니라 VIDEO DETAIL PAGE

  • 모든 댓글은 답글이 가능하도록 한다. ROOT COMMENT FORM은 화면 맨 아래에 있는 댓글 작성 폼이며, 댓글이나 답글에 나오는 폼은 COMMENT FORM이다.
  • Reply Comment에는 Single Comment와 ReplyComment가 같이 포함되어 답글이 무한정으로 늘어날 수 있게 하였다.



2. Comment model 생성

  • boilerplate-mern-stack-master\server\models 경로에 Comment.js 파일 생성 후, Subscriber.js 코드 복사 및 수정.

Comment.js (models)

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const commentSchema = mongoose.Schema({

    writer: { //작성자
        type: Schema.Types.ObjectId,
        ref: 'User'
    },
    postId: { //VideoId
        type: Schema.Types.ObjectId,
        ref: 'Video'
    },
    responseTo: {
        type: Schema.Types.ObjectId,
        ref: 'User'
    },    
    content: { //내용
        type: String
    }

}, { timestamps: true }) //만든날과 업데이트날 표시


const Comment = mongoose.model('Comment', commentSchema);

module.exports = { Comment }
  • root Reply Comment는 ReplyTo(responseTo)가 없다. 답글이 생성되면 root Reply Comment가 ReplyTo가 된다.



3. 비디오 디테일 페이지에 Comment Component 만들기

  • boilerplate-mern-stack-master\client\src\components\views\VideoDetailPage\Sections 경로에 Comment.js 파일 생성.

Comment.js (Sections)

import React from 'react'

function Comment() {
  return (
    <div>
        Comment
    </div>
  )
}

export default Comment

VideoDetailPage.js

// import Subscribe from './Sections/Subscribe'; //Subscribe.js를 연결
import Comment from './Sections/Comment';
//                        {/* Comments */}
                        <Comment />

 

 

 

댓글 기능 생성 (2) Comment.js

DetailVideoPage.js가 아니라 VideoDetailPage.js

  • Commet.js에서 구현한 Submit 버튼을 누르면 VideoDetailPage.js의 state에 Commens에 대한 모든 정보가 담긴다.



1. Comment.js를 위한 template 만들기

Comment.js (Sections)

import React from 'react'

function Comment() {
    return (
        <div>
            <br />
            <p> Replies </p>
            <hr />

            {/* Comment Lists */}

            {/* Root Comment Form */}

            <form style={{ display: 'flex' }} onSubmit >
                <textarea
                    style={{ width: '100%', borderRadius: '5px' }}
                    onChange
                    value
                    placeholder="코멘트를 작성해 주세요"

                />
                <br />
                <button style={{ width: '20%', height: '52px' }} onClick >Submit</button>    
            </form>
        </div>
    )
}

export default Comment



2. handleChange func 만들기

Comment.js (Sections)

//function Comment() {

    const [commentValue, setcommentValue] = useState("")

    const handleClick = (event) => {
        setcommentValue(event.currentTarget.value)
    }
                    onChange={handleClick}
                    value={commentValue}



3. OnSubmit func 만들고, 모든 Comment 정보들을 데이터베이스에서 가져오기

Comment.js (Sections)

import { useSelector } from 'react-redux';

function Comment(props) {

    const videoId = props.postId;
    const user = useSelector(state => state.user);
    const onSubmit = (event) => {
        event.preventDefault();     //버튼을 눌렀을 때 페이지 리프레시가 되지 않도록

        const variables = {
            content: commentValue,
            writer: user.userData._id, //redux에서 가져오는 방법 
            postId: videoId
        }

       Axios.post('/api/comment/saveComment', variables)
            .then(response => {
                if(response.data.success) {
                    console.log(response.data.result)
                    setCommentValue("")
                } else {
                    alert('커맨드를 저장하지 못했습니다.')
                }
            })
    }

//    return (
            <form style={{ display: 'flex' }} onSubmit={onSubmit} >
                <button style={{ width: '20%', height: '52px' }} onClick={onSubmit} >Submit</button>

VideoDetailPage.js

                        <Comment postId={videoId}/>
  • boilerplate-mern-stack-master\server\routes 경로에 comment.js 파일 생성 후, subscribe.js 코드 복사 및 수정.

comment.js

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

const { Comment } = require("../models/Comment");

//=================================
//             Comment
//=================================


router.post("/saveComment", (req, res) => {

    const comment = new Comment(req.body)

    comment.save((err, comment) => {
        if(err) return res.json({ success: false, err })
        
        Comment.find({'_id': comment._id}) //save를 쓸 때는 단독으로 populate를 쓸 수 없다.
            .populate('writer')
            .exec((err, result) => {
                if(err) return res.json({ success: false, err })
                res.status(200).json({ success: true, result })
            })
    })

});


module.exports = router;

index.js

//app.use('/api/subscribe', require('./routes/subscribe'));
app.use('/api/comment', require('./routes/comment'));



4. 저장된 댓글 데이터를 Parent Componet로 업데이트

  • SingleComment.js에서 submit한 데이터를 VideoDetailPage.js Comments state에 저장.

VideoDetailPage.js

    const refreshFunction = (newComment) => {
        setComments(Comments.concat(newComment))
    }
                    <Comment refreshFunction={refreshFunction} commentLists={Comments} postId={videoId}/>

VideoDetailPage.js

                if(response.data.success) {
                    console.log(response.data.result)
                    props.refreshFunction(response.data.result)



5. 콘솔창에서 댓글 리스트 확인
Comment.js (Sections)에 있는 요 코드로

                    console.log(response.data.result)

확인 완료~

 

 

 

댓글 기능 생성 (3) SingleComment

1. Comment.js에 SingleComment Component를 생성

Comment.js

// import { useSelector } from 'react-redux';
import SingleComment from './SingleComment';
            {/* Comment Lists */}
            <SingleComment />
  • boilerplate-mern-stack-master\client\src\components\views\VideoDetailPage\Sections 경로에 SingleComment.js 파일 생성.

SingleComment.js

import React from 'react'

function SingleComment() {
  return (
    <div>
        SingleComment
    </div>
  )
}

export default SingleComment



2. Single Comment를 위한 Templaet 생성

SingleComment.js

//import React from 'react'
import { Comment, Avatar, Button, Input } from 'antd';

const { TextArea } = Input;
        <div>
            <Comment
                actions
                author
                avatar={<Avatar src alt />}
                content
            />

            <form style={{ display: 'flex' }} onSubmit >
                <textarea
                    style={{ width: '100%', borderRadius: '5px' }}
                    onChange
                    value
                    placeholder="코멘트를 작성해 주세요"
                />
                <br />
                <button style={{ width: '20%', height: '52px' }} onClick >Submit</button>    
            </form>
        </div>



3. Open Reply func와 handleChange func 만들기

SingleComment.js

import React, { useState } from 'react'
function SingleComment() {

    const [OpenReply, setOpenReply] = useState(false)
    const [CommentValue, setCommentValue] = useState("")

    const onClickReplyOpen = () => { //답글 작성 폼 오픈
        setOpenReply(!OpenReply)
    }

    const onHandleChange = (event) => {
        setCommentValue(event.currentTarget.value)
    }

    const actions = [ //답글 작성 폼 숨김 기능
        <span onClick={onClickReplyOpen} key="comment-basic-reply-to">Reply to</span>
    ]
                actions={actions}
                        onChange={onHandleChange}
                        value={CommentValue}



4. onSubmit func 만들고, 모든 Comment 정보들을 데이터베이스에서 가져오기

  • Comment.js (Secitons)에서 onSubmit 코드를 가져와서 수정.

SingleComment.js

import { useSelector } from 'react-redux';
import Axios from 'axios'

const { TextArea } = Input;

function SingleComment(props) {

    const user = useSelector(state => state.user);
    const onSubmit = (event) => {
        event.preventDefault();

        const variables = {
            content: CommentValue,
            writer: user.userData._id, //redux에서 가져오는 방법 
            postId: props.postId,
            responseTo: props.comment._id //Comment.js의 onSubmit과 다른 부분
        }

        Axios.post('/api/comment/saveComment', variables)
            .then(response => {
                if(response.data.success) {
                    console.log(response.data.result)
                    setCommentValue("")
                } else {
                    alert('커맨드를 저장하지 못했습니다.')
                }
            })
    }
            <Comment
                actions={actions}
                author={props.comment.writer.name}
                avatar={<Avatar src={props.comment.writer.image} alt />}
                content={ <p> {props.comment.content} </p> }
            />
                <form style={{ display: 'flex' }} onSubmit={onSubmit} >
                    <button style={{ width: '20%', height: '52px' }} onClick={onSubmit} >Submit</button>


VideoDetailPage.js

    const [Comments, setComments] = useState([])

    useEffect(() => {
        
        Axios.post("/api/comment/getComments", variable)
            .then(response => {
                if(response.data.success) {
                    setComments(response.data.comments)
                } else {
                    alert('코멘트 정보를 가져오는 것을 실패하였습니다.')
                }
            })
                        <Comment commentLists={Comments} postId={videoId}/>

comment.js

router.post('/getComments', (req, res) => {

    Comment.find({ "postId": req.body.videoId })
        .populate('writer')
        .exec((err, comments) => {
            if(err) return res.status(400).send(err)
            res.status(200).json({ success: true, comments })
        })

});

콘솔을 통해 확인한 댓글 9개의 정보

Comment.js (Sections)

            {props.commentLists && props.commentLists.map((comment, index) => (
                (!comment.responseTo && //답글이 없는 댓글만 출력
                    <SingleComment comment={comment} postId={videoId}/>
                )
            ))}

회원가입 안 된 사람이 댓글 적어서 나는 오류. DB 삭제해 주면 됨.



5. 저장된 댓글을 Parent Component에 업데이트

  • SingleComment.js에서 submit한 데이터를 VideoDetailPage.js Comments state에 저장.

Comment.js

                    <SingleComment refreshFunction={props.refreshFunction} comment={comment} postId={videoId}/>
                    //오류난 코드 <SingleComment refreshFunction={refreshFunction} comment={comment} postId={videoId}/>

SingleComment.js

                if(response.data.success) {
                    console.log(response.data.result)
                    props.refreshFunction(response.data.result)

이놈자식이 날 6일동안 힘들게 한

오류 해결 안 돼서 주석 처리하고 다른 부분부터 하기로 했다...
refreshFunction={props.refreshFunction}으로 하니 해결.

근데 이건 무슨 오류지

 

 

 

댓글 기능 생성 (4) ReplyComment

1. ReplyComment Component를 Comment.js에 만들기

  • 리액트에서는 JSX를 HTML 대신 사용.

Comment.js

import ReplyComment from './ReplyComment';
                (!comment.responseTo && //답글이 없는 댓글만 출력
                    <React.Fragment>
                        <SingleComment refreshFunction={props.refreshFunction} comment={comment} postId={videoId}/>
                        <ReplyComment />
                    </React.Fragment>
                    //<SingleComment refreshFunction={refreshFunction} comment={comment} postId={videoId}/>
                )
  • boilerplate-mern-stack-master\client\src\components\views\VideoDetailPage\Sections 경로에 ReplyComment.js 파일 생성.

ReplyComment.js

import React from 'react'

function ReplyComment() {
    return (
        <div>
            ReplyComment
        </div>
    )
}

export default ReplyComment



2. ReplyComment 템플릿 만들기

ReplyComment.js

import React from 'react'
import SingleComment from './SingleComment'

function ReplyComment(props) {
    // 답글이 여러 개가 될 수 있으니까 변수로 만들기
    const renderReplyComment = (parentCommentId) =>
        props.commentLists.map((comment, index) => ( //댓글 정보 가져오기
            <React.Fragment> 
                {
                    comment.responseTo === parentCommentId &&
                        <div style={{ width: '80%', marginLeft:'40px'}}>
                            <SingleComment refreshFunction={props.refreshFunction} comment={comment} postId={props.videoId}/>
                            <ReplyComment commentLists={props.commentLists} parentCommentId={comment._id} postId={props.videoId}/>
                        </div>
                }
            </React.Fragment> //댓글 아이디와 아이디의 답글만 랜더링
    ))

    return (
        <div>
            
            <p style={{ fontSize: '14px', margin: 0, color: 'gray' }} onClick>
                View 1 more comment(s)
            </p>

            {renderReplyComment(props.parentCommentId)}

        </div>
    )
}

export default ReplyComment

Comment.js

                        <ReplyComment parentCommentId={comment._id} postId={videoId} commentLists={props.commentLists}/>



3. Calculate Child Comment Number, onHandleChange func 만들기

ReplyComment.js

import React, { useEffect, useState } from 'react'
import SingleComment from './SingleComment'

function ReplyComment(props) {

    const [ChildCommentNumber, setChildCommentNumber] = useState(0)
    const [OpenReplyComments, setOpenReplyComments] = useState(false)

    useEffect(() => { //답글이 몇 개인지

        let commentNumber = 0;
        
        props.commentLists.map((comment) => {
            if(comment.responseTo === props.parentCommentId) {
                commentNumber ++
            }
        })

        setChildCommentNumber(commentNumber)

    }, [])
    const onHandleChange = () => {
        setOpenReplyComments(!OpenReplyComments)
    }

    return (
        <div>

            {ChildCommentNumber> 0 &&
                <p style={{ fontSize: '14px', margin: 0, color: 'gray' }} onClick={onHandleChange}>
                View {ChildCommentNumber} more comment(s)
                </p>
            }

            {OpenReplyComments &&
                renderReplyComment(props.parentCommentId)
            }

        </div>
    )

 

Comment.js

                        <ReplyComment refreshFunction={props.refreshFunction} parentCommentId={comment._id} postId={videoId} commentLists={props.commentLists}/>

ReplyComment.js

                            <ReplyComment refreshFunction={props.refreshFunction} commentLists={props.commentLists} parentCommentId={comment._id} postId={props.videoId}/>



4. 기타 수정사항

  • useEffect의 마지막 줄 []를 비워두면 DOM이 Load될 때 한 번 실행. commentNumber가 바뀔 때마다 실행하게 하려면 props.CommentLists를 넣어야 한다.

ReplyComment.js

    useEffect(() => { //답글이 몇 개인지

        (중략)

    }, [props.commentLists])

 

  • 답글의 댓글 작성버튼 누르면 답글 작성 폼 숨김.

SingleComment.js

                    setCommentValue("")
                    setOpenReply(false)

 

고생했다 ㅎ
오류 발견... 3Depth DB 저장 시 postId 누락이라고 한다

 

728x90