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

코딩애플 - Next.js 로 게시판 수정/삭제 구현

by 쿠리의일상 2023. 6. 27.

게시글 수정

Dynamic route 기능으로 각 게시글의 수정 페이지를 만들어주기

게시글의 각각의 id 는 /modify/[id] 에 들어가므로, props.params.id 로 접근이 가능하다!

해당 id는 new ObjectId() 로 접근해서 findOne() 해준다.

<input defaultValue={result.title} name='title' required/>
<input defaultValue={result.content} name='content' required/>

찾아온 글을 input의 defaultValue 속성 안에 넣어주면 원래 글 내용이 잘 들어가진다.

Next.js 에선 value 대신 defaultValue 를 사용하는게 오류가 적다고 한다.

 

몽고DB로 원래 내용을 수정해주기

updateOne({ 조건 }, {$set : { 수정해줄 내용 }}) 형태로 만들어 준다.

 

이때, 전에 배웠던 3계층 구조 원리에 의하여 클라이언트 딴에서 DB를 직접 건드리는게 아닌,

서버에 먼저 보내고, 보낸 정보를 확인 후 DB를 변경할 수 있게 해준다.

 

id 값을 request 헤더에 같이 넣어 보내려면?

<h4>글수정</h4>
    <form action="/api/post/modify" method="POST" >
    <input defaultValue={result.title} name='title' required/>
    <input defaultValue={result.content} name='content' required/>
    <input name='id' value={props.params.id} style={{display: 'none'}}/>
    <button type="submit">수정하기</button>
</form>

id 값을 request 헤더에 넣으려면 위처럼 input 의 value 와 name 을 넣어주되, 스타일상 보이지 않게 처리해준다.

이 부분에서 따로 위의 방법이 아닌 방법이 뭐가 있을지 서칭해봤는데 안 나와서 잔머리 좀 썼는데 강의에서도 이렇게 해서 놀랐음 ㅇㅇ

 

즉, 서버에 필요한 데이터가 있다면
1. 유저에게 보내게끔 처리 (위의 방법)
2. DB 조회 해주기

 

 

게시글 삭제

게시글 목록에 자바스크립트를 사용한 애니메이션을 넣어주기 위해 'use client' 로 변경해주기

  • 리스트 목록의 큰 페이지는 서버 컴포넌트
  • 리스트의 기능이 들어갈 부분만 클라이언트 컴포넌트로

 

export default async function List() {
  const client = await connectDB;
  const boardList = await client.db('forum').collection('post').find().toArray();

  return (
    <div className="list-bg">
      <Link prefetch={false} href='/write'>글쓰기</Link>
      <ListItem />
    </div>
  )
}

ListItem 으로 목록 부분을 따로 빼준다.

 

클라이언트 컴포넌트에서 DB정보를 가져오는 방법 1

클라이언트 컴포넌트에서 처리해주는 방법(+useEffect 훅)이 있다.

useEffect 훅 안에선 DB에 직접 접근은 불가하므로, 서버를 통해서 DB 정보를 불러와준다.

useEffect(() => {
	// 서버에게 DB 게시글 목록을 요청해서 처리
}, []);

다만 이러한 경우엔 검색 노출이 어렵기 때문에 지양해준다.

왜냐하면, useEffect 의 특성상 return () 부분의 html 의 로딩이 끝난 이후에 실행되는데,

그러면 DB에서 불러온 정보가 html 이 이미 존재한 상태에서 html에 채워주는 순서이기 때문이다.

이러한 경우는 사람의 경우는 문제가 되지 않지만, 검색엔진 봇들의 경우가 문제가 된다.

 

클라이언트 컴포넌트에서 DB정보를 가져오는 방법 2

서버 컴포넌트에서 -> 클라이언트 컴포넌트에 props 로 전송해주기

export default async function List() {
  const client = await connectDB;
  const boardList = await client.db('forum').collection('post').find().toArray();

  return (
    <div className="list-bg">
      <Link prefetch={false} href='/write'>글쓰기</Link>
      <ListItem boardList={boardList} />
    </div>
  )
}
'use client'

import Link from "next/link"

export default function ListItem({ boardList }) {
  return (
    <>
      {boardList.reverse().map((el, idx) => {
        return (
          <div className="list-item" key={idx}>
            <Link href={'detail/' + el._id} prefetch={false} >
              <h4>{el.title}</h4>
            </Link>
            <p>{el.content}</p>
            <Link href={'/modify/' + el._id} prefetch={false}> ✏️ </Link>
          </div>
        )
      })}
    </>
  )
}

이렇게 되면 첫번째 방법보다 검색엔진 최적화가 잘될 것이다.

 

form 태그를 사용하지 않고 서버에 요청하는 방법 - Ajax

Ajax : Asynchronous Javascript and XML
서버와 통신하기 위해 XMLHttpRequest 객체를 사용하는 것을 의미
- 페이지 새로고침 없이 서버에 요청 (form 태그로 요청 시 항상 새로고침이 됨)
- 서버로부터 데이터를 받고 작업을 수행

fetch(), axious() 등을 사용해준다.

fetch(url, 옵션객체) => Promise 객체 반환(resolve/reject)

옵션 객체에는
- method
- headers (요청 헤더)
- body (요청 전문)
등을 설정할 수있다.

기본적으로 GET 방식으로 작동하므로 옵션 객체에 method 와, body 가 따로 없기에 써주지 않는다.
다만 POST 방식으로 fetch 를 사용하려면
{
method : 'POST',
headers : {
'Content-Type' : 'application/json',
},
body : JSON.stringify({
// 보내줄 정보를 마음껏 기재.
}),
}
위처럼 옵션 객체를 설정해줘야 한다.

삭제 버튼에 Ajax 를 아래처럼 사용해주었다. 정상 작동 한다. Delete method 도 가능하지만, Post 로 삭제해도 됨

fetch('/api/post/delete',{
    method : 'POST',
    headers : {
      'Content-Type' : 'application/json',
    },
    body : JSON.stringify({
      id : el._id,
    })
});

id 를 body에 보내줄 때 객체로 보내주려면 JSON.stringify() 로 처리해주고,

요청을 받아줄 땐 JSON.parse() 로 받아줘야 한다.

Ajax 요청 시 추가해줄 수 있는 기능들

fetch('/api/post/delete',{
    method : 'POST',
    headers : {
      'Content-Type' : 'application/json',
    },
    body : JSON.stringify({
      id : el._id,
    })
  })
  .then((response) => {
    if(response.status === 200)
      return response.json();
    else {
      // 서버 에러 코드 전송 시 실행할 부분
    }
  })
  .then((result) => {
    // 성공 시 실행할 부분
    console.log(result); // 서버에서 .json() 으로 보낸 메세지 확인 가능
  })
  .catch((err) => {
    // 인터넷 문제로 실패 시 실행할 부분
    console.log(err);
  });

 

에러 상황에는 2가지 상황이 존재한다.

  • 서버측에서 에러 코드(500 등)를 전송할 때 -> status가 200이 아닐 때로 구분!
  • 인터넷 등의 문제로 에러가 발생할 때 -> catch 부분!

 

 

삭제 예외처리 해주기

  if(requset.method == 'POST') {
    let result = await client.db('forum').collection('post').deleteOne({
      _id : new ObjectId(requset.body.id),
    });
    // 삭제를 시도할 때 그 결과값을 반환해준다. deletedCount 가 1이면 성공, 0이면 실패
    if(result.deletedCount === 1) {
      return response.redirect(302, '/list');
    } else if(result.deletedCount === 0) {
      return response.redirect(500, '/list');
    }
  }

 

삭제 애니메이션 넣기

애니메이션 동작 전 -> 동작 후 스타일 지정

  • 투명도 1 -> 0

Ajax 요청이 끝난 뒤(게시글 삭제가 되면)에 애니메이션을 추가로 준다.

이때, evt 로 이벤트 객체를 받아주고, 해당 이벤트 객체에 target 프로퍼티에 접근하면

onClick 등의 이벤트 핸들러가 실행된 html 객체 정보를 가져오게 된다.

evt.target // 클릭한 html 요소
evt.target.parentElement // 클릭한 html 요소의 부모 요소

요소에 접근하면, style 프로퍼티로 스타일 속성을 직접 변경해줄 수 있다.

evt.target.parentElement.style.opacity = 0;
setTimeout(() => {
  evt.target.parentElement.style.display = 'none';
}, 1000);

 

fetch 로 body 에 담아서 정보를 보내는 것이 아닌 쿼리스트링 사용하기

fetch(url + ?key=value

해당 정보는 서버측 url에서 request.query 로 접근이 가능하다.

쿼리스트링은 객체 상태로 키 : 값 으로 전해진다.

여러 정보를 담고 싶다면 &를 구분자로 처리해준다.

쿼리스트링은 GET 요청으로도 데이터 전송이 가능하다는 이점이 존재

다만 민감한 데이터를 넣어주면 주소창에 노출되므로 주의가 필요하다.

 

서버에서 다이나믹 라우팅하기 - URL parameter

위의 계층 구조에서 /api/ex/아무문자 로 요청이 들어오면 [key].js 가 실행된다.

이때 서버측에서 접근한 url 정보를 가져오려면 request.query 로 접근이 가능하다.

 

정리하자면... 클라이언트에서 서버로 정보를 보내는 방법에는,

1. Ajax, fetch 를 사용하는 경우, POST 메서드로 body 에 정보를 담아준다

2. form 태그에 action으론 url을 기재하고 submit 버튼을 기준으로 input 필드의 name 과 value 값을 정보에 담아서

3. query string - fetch의 url 부분에 ?와 &를 섞어서 키와 값을 담아서 GET 요청으로 정보를 담아주기

4. url parameter - fetch 의 url 부분에 value 를 적어주고, 서버측에서 [키].js 형식으로 정보를 전해주기

총 4가지 방법이 존재한다.