개인 프로젝트/블로그

댓글 수정 구현 + react에서 수정,삭제하기

공주맛밤 2022. 7. 31. 15:55

<댓글 수정(update) 구현 (spring boot)>

<replyUpdate>

@Transactional
    public Reply replyUpdate(ReplyUpdateDto replyUpdateDto) {
        Reply replyEntity = replyRepository.findById(replyUpdateDto.getId())
                .orElseThrow(()->new IllegalArgumentException("이미 삭제된 댓글입니다."));

        replyEntity.setContent(replyUpdateDto.getContent());

        return replyEntity;
    }

<replyUpdateDTO>

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ReplyUpdateDto {
    private Long id;
    private String content;
}

<replyApiController>

@PutMapping("/api/reply/{id}/update") // 댓글 수정
    public ResponseDto<?> replyUpdate (@RequestBody ReplyUpdateDto replyUpdateDto) {
        return new ResponseDto<>(HttpStatus.OK.value(), replyService.replyUpdate(replyUpdateDto));
    }

<react에서 수정, 삭제하기>

<board/Content 수정>

import React, { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Button, Card, Form, InputGroup } from "react-bootstrap";
import ReplyItem from "../../components/ReplyItem";

const Content = () => {
  const propsParam = useParams();
  const id = propsParam.id;
  const navigate = useNavigate();

  const [board, setBoard] = useState({
    //처음에 공백
    data: [],
    status: "",
  });

  const [replyList, setReplyList] = useState([]);

  const [reply, setReply] = useState({
    board_id: id,
    content: "",
  });

  const [subCategoryName, setSubCategoryName] = useState({});

  useEffect(() => {
    fetch("http://localhost:8080/board/" + id) //id를 통해서 게시판 정보를 가져옴
      .then((res) => res.json())
      .then((res) => {
        setBoard(res); //공백에 가져온 정보로 채워줌
        setSubCategoryName(res.data.subCategory);
        setReplyList(res.data.replies);
      });
  }, [id]);

  const deleteBoard = () => {
    //해당 게시글 삭제
    fetch("http://localhost:8080/api/board/" + id + "/delete", {
      method: "DELETE",
      headers: {
        AccessToken: localStorage.getItem("Tnut's accessToken"),
      },
    })
      .then((res) => res.text())
      .then((res) => {
        if (res === "success delete!") {
          navigate("/");
        } else {
          alert("삭제 실패");
        }
      });
  };

  const updateBoard = () => {
    //함수 updateBoard 실행되면 /update/id로 이동
    navigate("/board/updateForm/" + id);
  };

  const changeValue = (e) => {
    setReply((reply) => ({
      ...reply,
      content: e.target.value,
    }));
  };

  const submitReply = (e) => {
    e.preventDefault();
    fetch("http://localhost:8080/api/reply/save", {
      method: "POST",
      headers: {
        "Content-Type": "application/json; charset=utf-8",
      },
      body: JSON.stringify(reply),
    })
      .then((res) => {
        console.log(res);
        if (res.status === 200) {
          return res.json();
        } else {
          return null;
        }
      })
      .then((res) => {
        if (res !== null) {
          window.location.reload();
        } else {
          alert("댓글 등록 실패!");
        }
      });
  };

  return (
    <div>
      <h1>{board.data.title}</h1>
      <Button variant="secondary" onClick={() => updateBoard()}>
        수정
      </Button>{" "}
      <Button variant="secondary" onClick={() => deleteBoard()}>
        삭제
      </Button>{" "}
      <Button variant="secondary" onClick={() => navigate(-1)}>
        돌아가기
      </Button>
      <hr />
      <Card>
        <Card.Body>Category: {subCategoryName.subCategoryName}</Card.Body>
      </Card>
      <Card>
        <Card.Body>{board.data.content}</Card.Body>
      </Card>
      <hr />
      <InputGroup className="mb-3">
        <Form.Control
          type="reply"
          onChange={changeValue}
          name="reply"
          placeholder="댓글을 입력하세요"
        />
        <Button
          variant="outline-secondary"
          id="button-addon2"
          onClick={submitReply}
        >
          댓글 등록
        </Button>
      </InputGroup>
      <hr />
      <>
        {replyList.map((comment) => {
          return <ReplyItem key={comment.id} comment={comment} />;
        })}
      </>
    </div>
  );
};

export default Content;

<components/ReplyItem>

import React, { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Button, Card, Form, InputGroup } from "react-bootstrap";
import ReplyItem from "../../components/ReplyItem";

const Content = () => {
  const propsParam = useParams();
  const id = propsParam.id;
  const navigate = useNavigate();

  const [board, setBoard] = useState({
    //처음에 공백
    data: [],
    status: "",
  });

  const [replyList, setReplyList] = useState([]);

  const [reply, setReply] = useState({
    board_id: id,
    content: "",
  });

  const [subCategoryName, setSubCategoryName] = useState({});

  useEffect(() => {
    fetch("http://localhost:8080/board/" + id) //id를 통해서 게시판 정보를 가져옴
      .then((res) => res.json())
      .then((res) => {
        setBoard(res); //공백에 가져온 정보로 채워줌
        setSubCategoryName(res.data.subCategory);
        setReplyList(res.data.replies);
      });
  }, [id]);

  const deleteBoard = () => {
    //해당 게시글 삭제
    fetch("http://localhost:8080/api/board/" + id + "/delete", {
      method: "DELETE",
      headers: {
        AccessToken: localStorage.getItem("Tnut's accessToken"),
      },
    })
      .then((res) => res.text())
      .then((res) => {
        if (res === "success delete!") {
          navigate("/");
        } else {
          alert("삭제 실패");
        }
      });
  };

  const updateBoard = () => {
    //함수 updateBoard 실행되면 /update/id로 이동
    navigate("/board/updateForm/" + id);
  };

  const changeValue = (e) => {
    setReply((reply) => ({
      ...reply,
      content: e.target.value,
    }));
  };

  const submitReply = (e) => {
    e.preventDefault();
    fetch("http://localhost:8080/api/reply/save", {
      method: "POST",
      headers: {
        "Content-Type": "application/json; charset=utf-8",
      },
      body: JSON.stringify(reply),
    })
      .then((res) => {
        console.log(res);
        if (res.status === 200) {
          return res.json();
        } else {
          return null;
        }
      })
      .then((res) => {
        if (res !== null) {
          window.location.reload();
        } else {
          alert("댓글 등록 실패!");
        }
      });
  };

  return (
    <div>
      <h1>{board.data.title}</h1>
      <Button variant="secondary" onClick={() => updateBoard()}>
        수정
      </Button>{" "}
      <Button variant="secondary" onClick={() => deleteBoard()}>
        삭제
      </Button>{" "}
      <Button variant="secondary" onClick={() => navigate(-1)}>
        돌아가기
      </Button>
      <hr />
      <Card>
        <Card.Body>Category: {subCategoryName.subCategoryName}</Card.Body>
      </Card>
      <Card>
        <Card.Body>{board.data.content}</Card.Body>
      </Card>
      <hr />
      <InputGroup className="mb-3">
        <Form.Control
          type="reply"
          onChange={changeValue}
          name="reply"
          placeholder="댓글을 입력하세요"
        />
        <Button
          variant="outline-secondary"
          id="button-addon2"
          onClick={submitReply}
        >
          댓글 등록
        </Button>
      </InputGroup>
      <hr />
      <>
        {replyList.map((comment) => {
          return <ReplyItem key={comment.id} comment={comment} />;
        })}
      </>
    </div>
  );
};

export default Content;

<components/SubReplyItem>

import React, { useState } from "react";
import { Button, Card, Form, InputGroup } from "react-bootstrap";

const SubReplyItem = (props) => {
  const { content, id } = props.subreply;

  const [mode, setMode] = useState("read");

  const [contentValue, setContentValue] = useState({
    id: id,
    content: content,
  });

  const changeValue = (e) => {
    setContentValue((contentValue) => ({
      ...contentValue,
      content: e.target.value,
    }));
  };

  const deleteReply = () => {
    fetch("http://localhost:8080/api/reply/" + id + "/delete", {
      method: "DELETE",
    })
      .then((res) => res.text())
      .then((res) => {
        console.log(res);
        if (res === "success delete!") {
          window.location.reload();
        } else {
          alert("삭제 실패");
        }
      });
  };

  const updateReply = (e) => {
    e.preventDefault();
    fetch("http://localhost:8080/api/reply/" + id + "/update", {
      method: "PUT",
      headers: {
        "Content-Type": "application/json; charset=utf-8",
      },
      body: JSON.stringify(contentValue),
    })
      .then((res) => {
        if (res.status === 200) {
          return res.json;
        } else {
          return null;
        }
      })
      .then((res) => {
        if (res !== null) {
          window.location.reload();
        } else {
          alert("댓글 수정 실패!");
        }
      });
  };

  return (
    <div>
      <Card>
        {mode === "read" ? (
          <>
            {content !== null ? (
              <Card.Body>
                {content}
                {"   "}
                <Button variant="outline-secondary" onClick={deleteReply}>
                  삭제
                </Button>{" "}
                <Button
                  variant="outline-secondary"
                  onClick={() => setMode("update")}
                >
                  수정
                </Button>
              </Card.Body>
            ) : (
              <Card.Body>
                "삭제된 댓글입니다."
                {"   "}
                <Button variant="outline-secondary" onClick={deleteReply}>
                  삭제
                </Button>{" "}
                <Button
                  variant="outline-secondary"
                  onClick={() => setMode("update")}
                >
                  수정
                </Button>
              </Card.Body>
            )}
          </>
        ) : (
          <InputGroup className="mb-3">
            <Form.Control
              type="replyUpdate"
              onChange={changeValue}
              name="replyUpdates"
              placeholder="글을 입력하세요"
              value={contentValue.content}
            />
            <Button
              variant="outline-secondary"
              id="button-addon2"
              onClick={updateReply}
            >
              수정하기
            </Button>
            <Button
              variant="outline-secondary"
              id="button-addon2"
              onClick={() => setMode("read")}
            >
              수정취소
            </Button>
          </InputGroup>
        )}
      </Card>
    </div>
  );
};

export default SubReplyItem;

<블로그에서 확인하기>

기본 모습
자식이 있는 댓글 삭제
수정버튼을 누르면 원래 데이터를 담고 있는 input나타남
대댓글 수정하기도 마찬가지
수정취소를 누르면 기존데이터를 담고 있는 일반 card가 나타남
대댓글 수정 모습
댓글 수정하기 (근데 삭제된 댓글을 수정하는게 맞나...?)

db에서 데이터가 지워진 것이 아니고 content가 null인것이기 때문에 만일 삭제된 댓글이 수정가능하도록 하려면 isDeletable도 함께 업데이트 시켜주거나 아니면 수정이 되지 않도록 리팩토링 해야겠다

삭제된 댓글의 대댓글 삭제
함께 삭제됨


앞으로 할 내용 : 댓글에 user정보를 담아서 해당 유저 + 관리자에 한해서 삭제하고 수정할 수 있게끔 만들 것임, 조건부 렌더링이 생각보다 재밌고 편해서 category부분도 리펙터링 할 예정

+ 삭제된 댓글은 수정 안되도록

728x90
반응형