티스토리 뷰

▶게시물 댓글달기 기능 구현

이번 글에서는 게시물에 댓글달기 버튼을 누르면 그때 댓글 컴포넌트가 보이고

그 댓글 컴포넌트에 댓글을 작성/편집/삭제 하는 기능을 구현해 보려고 합니다.

 

📌 화면 구성

게시물 컴포넌트 하단에 있는 댓글 달기 버튼을 눌렀을 때 댓글 컴포넌트가 보이도록 합니다.

그리고 textarea에 댓글을 작성하고 저장하기 버튼을 누르면 바로 위 댓글 리스트에 보여지게 합니다.

또한 기존 댓글 각각 편집과 삭제 버튼이 존재하며

편집을 누르면 해당 댓글의 내용이 아래 textarea에 오면서 저장 버튼의 컬러가 바뀌도록 css까지 지정해 주었습니다.

삭제 버튼 역시 버튼을 누르는 동시에 화면에서 사라지게 했습니다.

댓글 달기 버튼을 눌렀을 때의 화면 구성
특정 댓글 편집 버튼을 눌렀을 때

 

728x90

📌 데이터베이스와 주고 받아야 할 내용

댓글을 작성하고 저장 버튼을 누른 시점 기준으로 클라이언트가 서버에게 알려주어야 하는 요소들은 (post 요청)

어느 homeid 게시물 아래 댓글을 달았는지 → homeid

댓글의 내용 → text

 

댓글을 수정하였을 때는 (put 요청)

어느 homeid 게시물 아래 댓글을 수정하였는지 → homeid

댓글의 내용 → text

 

댓글을 삭제할 때는 (delete 요청)

지금 댓글들은 데이터베이스에서 homeid를 FK로 사용하고 있지만 PK로 cmtid라는 것을 자동으로 부여(AUTO_INCREMENT)받고 있었습니다.

그래서 화면이 처음 렌더링 되었을 때 homeid를 통해 해당하는 댓글을 모두 가져왔을 때 그 댓글들의 cmtid도 같이 가져와지면 그것을 가지고 삭제할 때도 cmtid로 삭제를 해야 합니다.

comment 테이블

 

📌 댓글 컴포넌트 추가

댓글 컴포넌트는 CardBox 컴포넌트의 자식 컴포넌트가 됩니다.

다시 말해서, CardBox 컴포넌트에서 댓글 달기 버튼을 눌렀을 때

CommentBox 컴포넌트가 보이게 될 것이고, 이때 props로 해당 CardBox의 homeid와 comment들을 전달해줍니다.

추가로 댓글을 추가/편집/삭제 했을 때 바로바로 화면이 바뀌기 위해 화면을 리프레시 하는 함수도 넘겨줄 겁니다.

 

우선 CardBox 컴포넌트부터 살펴봅시다.

위에서 말했다 싶이 댓글 버튼을 눌렀을 때 CommentBox 컴포넌트가 보이도록 합니다.

그렇기 때문에 show라는 state,

CardBox 렌더 되었을 때 해당하는 homeid의 모든 댓글을 불러와 가지고 있을 리스트 형태의 comment라는 state

 

총 2가지의 state가 필요합니다.

 

우선, 이번에는 가장 상위 부모 컴포넌트부터 차근차근 살펴보도록 합시다.

export default function Home(props) {
  const [array, setArray] = useState([]);
  
  // 화면이 처음 렌더링 되었을 떄
  // 모든 게시물의 내용을 가져옴
  useEffect(() => {
    axios.get('/api/home').then(res => {
      setArray(res.data.result);
    });
  }, []);
  
  // 게시물의 세부 내용이 업데이트 될 떄
  // 해당 함수를 호출하여
  // 업데이트된 게시물의 내용을 다시 불러와
  // 화면도 바뀔 수 있도록
  const onRefreshHome = () => {
    axios.get('/api/home').then(res => {
      setArray(res.data.result);
    });
  };
  
  return (
    <>
      <Header name="home" />

      <section className="home-layer">
        <ul className="list">
          {array &&
            array.map((item, index) => {
              //console.log(item);
              return (
              
                // CardBox 컴포넌트에게
                // homeid, 그 게시물의 내용들(value), 리프레시 함수를 넘김
                <CardBox
                  key={item.homeid}
                  value={item}
                  onRefresh={onRefreshHome}
                />
              );
            })}
        </ul>
      </section>
    </>
  );
  
  const CardBox = (props) => {
    const { homeid, likecount, title, subtitle, tags, url, text, image } = props.value;
    
    const [show, setShow] = useState(false); // 댓글달기 버튼이 눌릴 때 false -> true
    const [comment, setComment] = useState([]); // homeid에 해당하는 모든 댓글을 담아놓을
    
    useEffect(() => {
      axios.get('api/home/comment', { params: { homeid: homeid }})
        .then(res => {
          setComment(res.data.result)
        })
    },[])
    
    const onClickLike = () => {
      axios.put('/api/home/like', { homeid: homeid, likecount: likecount })
        .then(res => {
          props.onRefresh();
        });
    };
    
    const onClickComment = () => {
      setShow(!show); // true => false, false => true
    };
    
    // 댓글이 작성/편집/삭제 되었을 때
    // 해당 함수를 호출하여
    // 업데이트된 댓글들을 불러와 화면에도 업데이트
    const onRefresh = () => {
      axios.get('/api/home/comment', { params: { homeid: homeid }} )
        .then(res => {
          setComment(res.data.result);
        });
    };
    
    return (
    <li>
      <div className="card">
        <div className="head">
          <div>
            <Image src={EDU_ICON} alt="광고 아이콘" />
            <span className="title">{title}</span>
            <Image className="more" src={MORE_ICON} alt="더보기 메뉴" />
          </div>
          <div className="text">
            <p>{subtitle}</p>
            <p className="blue">{tags}</p>
          </div>
        </div>
        <div className="body">
          <div className="image">
            <Image src={image} alt="광고 메인 이미지" />
          </div>
          <div className="text">
            <div>
              <p className="grey sm">{url}</p>
              <p className="bold">{text}</p>
            </div>
            <button>더 알아보기</button>
          </div>
        </div>
        <div className="foot">
          <div className="btn-box active">
            <div>
              <Image src={HOME_ICON} alt="홈 바로가기" />
              <span className="btn-text" onClick={onClickLike}>
                좋아요({likecount})
              </span>
            </div>
          </div>
          <div className="btn-box">
            <div>
              <Image src={YOUTUBE_ICON} alt="동영상 바로가기" />
              <span className="btn-text" onClick={onClickComment}>
                댓글 달기
              </span>
            </div>
          </div>
          <div className="btn-box">
            <div>
              <Image src={PEOPLE_ICON} alt="사용자 바로가기" />
              <span className="btn-text">공유 하기</span>
            </div>
          </div>
        </div>
        
        // show가 true일 때만 CommentBox 컴포넌트가 보여지게 됨
        {show === true && (
          <CommentBox homeid={homeid} onRefresh={onRefresh} list={comment} />
        )}
      </div>
    </li>
   );
  };
  
  const CommentBox = (props) => {
    const [text, setText] = useState('');
    const [selectedItem, setSelectedItem] = useState(null);
    
    const onChangeText = (e) => {
      setText(e.target.value);
    };
    
    const onClickSave = () => {
      axios.post('/api/home/comment', { homeid: props.homeid, text: text })
        .then(res => {
          setText(''); // 저장하면 댓글창 클리어 해주기
          props.onRefresh && props.onRefresh();
        });
    };
    
    const onClickRemove = (cmtid) => {
      axios.delete('/api/home/comment', { params: { cmtid: cmtid } })
        .then(res => {
          props.onRefresh && props.onRefresh();
        });
    };
    
    // 편집하기 버튼을 누르게 되면
    // selectedItem이 해당 댓글의 cmtid, homeid, text로 state를 set
    const onClickEdit = (item) => {
      setSelectedItem(item);
    }
    
    // 위에서 selectedItem에 담긴 cmtid, homeid, text를 이용해서
    // text 부분을 수정해줌
    const onChangeEdit = (e) => {
      const item = { ...selectedItem };
      item.text = e.target.value;
      setSelectedItem(item);
    }
    
    // 위에서 선택되고, 수정된 내용을
    // put 요청으로 댓글을 수정
    const onClickUpdate = () => {
      axios.put('/api/home/comment', { cmtid: selectedItem.cmtid, text: selectedImte.text })
        .then(res => {
          selectedItem(null);
          props.onRefresh && props.onRefresh();
        });
    };
    return (
    <div className="comment-box">
      <ul>
        {props.list &&
          props.list.map((item) => {
            return (
              <li key={item.cmtid}>
                {item.text}
                <div className="buttons">
                  <Button
                    type="primary"
                    onClick={() => onClickEdit(item)}
                    text="편집"
                  ></Button>
                  <Button
                    type="secondary"
                    onClick={() => onClickRemove(item.cmtid)}
                    text="삭제"
                  ></Button>
                </div>
              </li>
            );
          })}
      </ul>
      <div className="input-box">
        {selectedItem ? (
          <>
            {/* 편집을 위한 화면 */}
            <textarea onChange={onChangeEdit} value={selectedItem.text} />
            <Button type="secondary" onClick={onClickUpdate} text="저장" />
          </>
        ) : (
          <>
            {/* 삽입을 위한 화면 */}
            <textarea
              placeholder="여기에 내용을 입력하세요"
              onChange={onChangeText}
              value={text}
            />
            <Button type="primary" onClick={onClickSave} text="저장" />
          </>
        )}
      </div>
    </div>
  );
  }
}

지금 클라이언트에서 /api/home/comment 이라는 url로 get요청과 post 요청과 put 요청, delete 요청을 했으니,

맞게 서버에서도 코드를 작성해줍니다.

 

SMALL

 

📌 서버 → 클라이언트

서버는 클라이언트가 보낸 데이터를 가지고 요청한 대로 일을 처리해 줍시다.

 

📄 src/api/index.js 파일 & src/api/maria.js 파일

 

1️⃣ get 요청 (클라이언트로부터 받은 homeid을 조건으로 댓글 불러오기)

// index.js
router.get('/home/comment', async (req, res) => {
  const array = await mysql.selectComment(req.query);
  res.send({ result: array });
});
// maria.js
Maria.selectComment = (params) => {
  return new Promise(async (resolve) => {
    const { homeid } = params;
    const sql = `select * from comment where homeid=${homeid};`;
    
    const result = await queryFunc(sql);
    resolve(result);
  });
};

 

2️⃣ post 요청 (클라이언트로 부터 받은 homeid와 text로 comment 테이블에 댓글 삽입)

// index.js
router.post('/home/comment', async (req,res) => {
  await mysql.insertComment(req.body);
  res.send({ result: 'success' });
});
// maria.js
Maria.insertComment = (params) => {
  return new Promise(async (resolve) => {
    const { homeid, text } = params;
    
    const sql = `insert into comment (homeid, text) values(${homeid}, '${text}');`;
    const result = await queryFunc(sql);
    resolve(result);
  });
};

 

3️⃣ put 요청 (클라이언트로부터 받은 cmtid, text로 해당 댓글 내용 update)

// index.js
router.put('/home/comment', async (req,res) => {
  await mysql.updateComment(req.body);
  res.send({ result: 'success' });
});
// maria.js 파일
Maria.updateComment = (parmas) => {
  return new Promise(async (resolve) => {
    const { cmtid, text } = params;
    
    const sql = `update commnet set text='${text}' where cmtid=${cmtid};`;
    const result = await queryFunc(sql);
    resolve(result);
  });
};

 

4️⃣ delete 요청 (클라이언트로부터 받은 cmtid로 해당 댓글 delete)

// index.js
router.delete('/home/comment', async (req,res) => {
  await mysql.deleteComment(req.query);
  res.send({ result: 'success' });
});
// maria.js
Maria.deleteComment = (params) => {
  return new Promise(async (resolve) => {
    const { cmtid } = params;
    const sql = `delete from comment where cmtid=${cmtid};`;
    
    const result = await queryFunc(sql);
    resolve(result);
  });
};

 

이것으로 댓글 달기 기능 구현도 끝입니다 :)

728x90
LIST
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함