본문 바로가기
Web Study/Next.js

코딩 애플 - 댓글

by 쿠리의일상 2023. 7. 3.

CSR 으로 댓글 기능 구현하기

먼저, 댓글은 상세 페이지 안쪽에 존재하여야 한다.

여기서 선택을 해줄 수 있는데 서버 컴포넌트 형식으로 댓글 기능을 만들어주면 서버에 연동해줄 때마다 페이지의 새로고침이 필요해질 수 있다. ---> 이때 form 태그를 사용해서 input 을 받아준다.

 

사용자 경험을 향상시키기 위해 새로고침 없이 바로 적용이 되게하려면 (Ajax 등을 사용) CSR, 즉 use client 로 댓글 부분을 따로 빼주자.

'use client'

import React from 'react'

export default function Comment(props) {
  return (
      <div>
        <div>댓글목록</div>
        <input />
        <button>댓글전송</button>
      </div>
  )
}

 

useState 로 입력해준 값 다루기

export default function Comment(props) {
  const[comment, setComment] = useState('');

  return (
      <div>
        <div>댓글목록</div>
        <input onChange={(evt) => {
          if(evt.target.value === '') return;

          setComment(evt.target.value);
        }}/>
        <button onClick={() => {
          fetch('', {
            method : 'POST',
            body : comment,
          })
        }}>댓글전송</button>
      </div>
  )
}

onChange 와 useState 를 사용하여 input의 value 를 저장해준 다음, 전송 버튼을 눌렀을 때 fetch 가 실행되게 만들어준다.

이렇게 해주면 form 태그 없이, 새로고침도 없이 서버에 body에 담긴 정보를 보내줄 수 있다.

 

서버를 구성하기 전에, 덧글을 어느 collection 에 담아야 할까?

기본적으로 forum db안 post collection의 document 에 comment 를 담는게 베스트지만

그렇게 되면 덧글이 무수히 많아졌을 때 각 document 는 개당 8mb가 최대 용량이므로 댓글을 전부 담을 수 없을 수도 있다.

그리고 덧글을 찾아야할 필요가 있을 때 비효율적일 수 있다고 한다.

 

그렇다면 덧글 정보를 담은 collection을 따로 만들어준 다음 -> 그 컬렉션 안 document 에 comment 를 독립적으로 담아주는 방법도 좋다. 덧글 정보 안에 어떤 게시글의 아이디(ObjectId)를 담아주면 된다.

 

정리하자면, DB 저장 시 이게 맞는지 모르겠다?

  • 나중에 수정/삭제/출력이 쉬우면 OK
  • 수정/삭제/출력이 어렵다면 -> 다른 document 로 분리해본다.

 

덧글 전송하기

export default async function handler(request, response) {
  let session = await getServerSession(request, response, authOptions);

  if(request.method === 'POST') {
    const client = await connectDB;
    
    console.log(request.body);
    const newComment = {
      ...request.body,
      author : session.user.email,
    }
    await client.db('forum').collection('comment').insertOne(newComment);

    return response.redirect(302, `/detail/${request.body.url}`);
  }
}

getServerSession 함수는 요청과 응답을 추가로 파라미터로 전달해줘야 하므로 넣어준 다음 몽고디비에 insertOne 으로 넣어준다.

 

댓글 조회기능

상세 페이지에 접속하면 보여지는 댓글을 Create 했으므로 이제 Read 해준다.

보통은 부모 detail page.js 에서 조회를 해주고 props 로 넘겨주는 것이 간단하게 처리할 수 있지만

클라이언트 컴포넌트에서 DB데이터를 직접 가져와서 보여주는 방법으로 만들어본다.

 

물론 클라이언트 컴포넌트 안에선 DB 로 출력을 못하므로 서버에게 부탁하는 방식으로 코드를 만들어주고, useEffect 안에서 Ajax 로 처리를 해준다.

useEffect 는 쓸데없는 코드 보관함이라고 보면 된다.
Ajax 나 타이머 같은 기능들을 넣고 싶을 때 사용한다.

 

  • useEffect 안에 적은 코드는 html 이 로드/재렌더링 될 때마다 실행
  • useEffect 안의 코드는 html 렌더링 후에 실행됨 -> 즉 useEffect 와 Ajax 로 데이터를 불러오더라도 일단 html 이 먼저 보이기 때문에 아무것도 보이지 않으면 사용자 경험이 좋지 않을 수 있음 -> html 일부분이라도 먼저 보여주는 작업이 필요하다.

 

  useEffect(() => {
    async function commentFetch() {
      await fetch(`/api/comment/list?id=${id}`)
        .then((r) => r.json())
        .then((result) => {
          setCommentList(result);
        })
    }

    commentFetch();
  }, []);

이처럼 클라이언트 컴포넌트 안에서 db 정보를 서버에게 요청할 때 쿼리스트링을 사용해준다.

export default async function handler(request, response) {
  if(request.method === 'GET') {

    const client = await connectDB;
    const result = await client.db('forum').collection('comment').find({ parent : new ObjectId(request.query.id) }).toArray();

    return response.status(200).json(result);
  }
}

그럼 서버에서 응답 받은 내용(json(result))가

우선 r => r.json() 형태로 올 것이고 이 리턴값을 다음 then의 파라미터로 받아서 값을 정상적으로 받을 수 있다.

<div>댓글목록</div><hr/>
    <ul>
      {
        commentList.length > 0 ?
        commentList === [] ? null : commentList.map((el, idx) => {
          return (
            <li key={idx} >{el.author} : {el.content}</li>
          )
        }) : '로딩 중입니다.'
      }
    </ul>

 

댓글을 작성하면 바로 적용시키기

이는 댓글 작성 때 만들었던 서버 기능을 추가해서 구현해준다.

export default async function handler(request, response) {
  let session = await getServerSession(request, response, authOptions);

  if(request.method === 'POST') {
    if(session === 'null') return;

    const client = await connectDB;
    request.body = JSON.parse(request.body);
    
    const newComment = {
      parent : new ObjectId(request.body.parent),
      content : request.body.content,
      author : session.user.email,
    }

    await client.db('forum').collection('comment').insertOne(newComment);
    const newCommentList = await client.db('forum').collection('comment').find({ parent : new ObjectId(request.body.parent )}).toArray();
    return response.json(newCommentList);
  }
}

위처럼 response 에 새로운 댓글 리스트를 json에 담아서 반환해주고,

아래처럼 then 으로 받아서 state 를 변경시켜주면 바로 반영된다.

<button onClick={() => {
  const fetchData = {
    parent : id,
    content : comment,
  }

  fetch('/api/comment/new', {
    method : 'POST',
    body : JSON.stringify(fetchData),
  })
    .then(r => r.json())
    .then((result) => {
      setCommentList(result);
    });
  inputRef.current.value = '';
}}>댓글전송</button>