이전글
https://greed-yb.tistory.com/322
이번엔 게시글을 삭제해 보자
boardRead.tsx
import React, {useEffect, useState} from "react";
import axios from "axios";
import {Link, useNavigate, useParams} from "react-router-dom";
import Cookies from 'js-cookie';
import { useUserContext } from '../utils/UserInfo';
// 게시판 변수 타입 설정
interface BoardRead {
title: string;
contents: string;
writer: string;
}
function BoardRead() {
// navigate 훅 사용
const navigate = useNavigate();
// 전역변수로 저장한 login 한 사용자 username
const { username } = useUserContext();
// Route path 로 받은 no 값을 받아온다
const { no } = useParams();
const [board, setBoarRead] = useState<BoardRead>({
title: "",
contents: "",
writer: ""
});
// readOnly 제어하기
const [ readOnly, setReadOnly ] = useState({
title: true,
writer: true,
contents: true
})
// button display 제어하기
const [ displayButton , setDisplayButton ] = useState(false);
useEffect(() => {
axios.get("/api/boardRead" , {
params : {
no
}
}).then((response => {
setBoarRead(response.data);
// 로그인한 사용자와 글 작성자가 같을때
if(response.data.writer == username){
setReadOnly((prevState) => ({
...prevState, // 이전 데이터를 유지
title: false, // 글쓴이가 일치한다면 해제
writer: true, // 글쓴이는 계속 true
contents: false, // 글쓴이가 일치한다면 해제
}));
// 저장 버튼 display
setDisplayButton(true);
}
})).catch((error) => {
console.log(error)
})
}, [no , username]) // 글번호와 로그인한 유저정보가 변경될 경우
// 태그의 name 을 인식해서 값을 가져온다
// HTMLInputElement 와 HTMLTextAreaElement 으로 type 을 지정한다
const dataCheck = ({ target: { value, name} }: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> ) => {
// readOnly 가 해제 되었을때 setBoarRead 로 값을 동기화 해야 텍스트를 입력할 수 있다
setBoarRead((prevBoard) => ({
...prevBoard, // 이전 상태의 데이터를 유지
[name]: value, // 일치하는 데이터만 변경
}));
}
const axiosSave : () => Promise<void> = async () => {
const token = document.cookie;
if (!token){
// token 이 없다면 로그인 화면으로
navigate('/login');
}else{
// npm install js-cookie
// npm install @types/js-cookie
// Cookies 에서 바로 token 을 가져올 수 있게 js-cookie 를 설치해서 사용하였다
let checkToken : string | undefined = "";
if(!Cookies.get('refreshToken')){
// refreshToken 이 없다면 로그인 화면으로
navigate('/login');
}else{
checkToken = Cookies.get('refreshToken')
}
await axios.post("http://localhost:7172/api/boardSave" , board ,{
params: {checkToken}
}
).then((response) => {
if(response.data == "success"){
alert("저장하였습니다.");
navigate('/board');
}
}).catch((error) => {
alert("저장 실패하였습니다.");
})
}
}
const axiosDelete : () => Promise<void> = async () =>{
if(!confirm("삭제하시겠습니까?")){
return;
}
await axios.delete("http://localhost:7172/api/boardDelete",{
params: {no} // useParam 으로 받아온 글 번호
}).then((response)=> {
if(response.data == "success"){
alert("해당 게시글 삭제 완료");
navigate('/board');
}else {
console.log("오류가 발생했습니다.");
}
}).catch((error)=>{
console.log(error);
})
}
return (
<main id="main" className="main">
<div className="pagetitle">
<h1>게시판</h1>
<nav>
<ol className="breadcrumb">
<li className="breadcrumb-item"><Link to="/">Home</Link></li>
<li className="breadcrumb-item">Forms</li>
<li className="breadcrumb-item active">Layouts</li>
</ol>
</nav>
</div>
<section className="section">
<div className="row">
<div className="col-lg-6">
<div className="card">
<div className="card-body">
<h5 className="card-title">상세보기</h5>
<form className="row g-3">
<div className="col-6">
<label htmlFor="title" className="form-label">제목</label>
<input type="text" className="form-control" id="title" name="title" value={board.title} onChange={dataCheck} readOnly={readOnly.title} />
</div>
<div className="col-6">
<label htmlFor="writer" className="form-label">글쓴이</label>
<input type="text" className="form-control" id="writer" name="writer" value={board.writer} onChange={dataCheck} readOnly={readOnly.writer} />
</div>
<div className="col-12">
<label htmlFor="contents" className="form-label">내용</label>
<textarea className="form-control" id="contents" name="contents" style={{height: "200px"}} value={board.contents} onChange={dataCheck} readOnly={readOnly.contents}></textarea>
</div>
<div className="text-center">
<button type="button" className="btn btn-primary" onClick={axiosSave} style={{display: displayButton ? `inline-block` : `none`}} >저장</button>
<button type="button" className="btn btn-danger" onClick={axiosDelete} style={{display: displayButton ? `inline-block` : `none`}} >삭제</button>
<Link to="/board" className="btn btn-secondary">뒤로</Link>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
</main>
);
}
export default BoardRead;
const axiosDelete : () => Promise<void> = async () =>{
if(!confirm("삭제하시겠습니까?")){
return;
}
console.log("삭제");
}
삭제 전에 confirm 을 이용하여 삭제 여부를 체크 후
삭제를 하려고 하니 오류가 발생한다
해당 오류는 ESLint 의 규칙 중에
no-restricted-globals 에 의해 발생되어
confirm, alert, prompt 등의 함수 사용을 제한한다고 한다
ESLint 설정에서 confirm 을 제외하면 된다는데
나는 해당 파일이 보이지 않아서
package.json 에 다음과 같이 설정하였다
"rules": {
"no-restricted-globals": ["error", "event", "fdescribe"]
}
재시작을 해주면 적용된다
혹시나 적용이 되지 않는다면
루트 폴더에 .eslintrc.js 파일을 생성해 준다
module.exports = {
extends: [
"react-app",
"react-app/jest"
],
rules: {
"no-restricted-globals": ["error", "event", "fdescribe"] // confirm 제외
}
};
그리고 다시 재시작하자
이전글에서 삭제 메서드와 삭제 버튼이 추가되었다
Controller
@DeleteMapping("/boardDelete")
public String boardDelete(@RequestParam Long no) throws Exception {
try {
boardRepository.deleteById(no);
return "success";
}catch (Exception e){
e.printStackTrace();
return "fail";
}
}
TEST
삭제 여부 confirm 이 발생하고
완료되었다는 alert 이 실행되었다
navigate 로 인하여 게시판 목록 페이지로 이동되었고
해당 글은 조회되지 않고 있다
글 삭제 맞게 DB 에서도 deleteById 로 삭제를 진행하였지만
개인적으로 물리적 삭제보다는 사용여부의 Y/N 으로
데이터 제어하는 쪽을 선호 하는 편이다
각자에 맞춰서 사용하자
'개발 > Spring' 카테고리의 다른 글
[SpringBoot] React + TypeScript + JPA 프로젝트 (13) - Chart.js 적용하기 (0) | 2024.11.24 |
---|---|
[SpringBoot] React + TypeScript + JPA 프로젝트 (12) - 로그아웃 하기 (0) | 2024.11.23 |
[SpringBoot] React + TypeScript + JPA 프로젝트 (10) - 게시판 만들기(수정하기) (0) | 2024.11.21 |
[SpringBoot] React + TypeScript + JPA 프로젝트 (9) - 게시판 만들기(상세보기) (0) | 2024.11.20 |
[SpringBoot] React + TypeScript + JPA 프로젝트 (8) - useContext 전역변수 다루기 (0) | 2024.11.19 |
댓글