본문 바로가기
개발/Spring

[SpringBoot] React + TypeScript + JPA 프로젝트 (11) - 게시판 만들기(삭제하기)

by 코딩하는 흰둥이 2024. 11. 22.

이전글

https://greed-yb.tistory.com/322

 

[SpringBoot] React + TypeScript + JPA 프로젝트 (9) - 게시판 만들기(상세보기)

이전글https://greed-yb.tistory.com/321 [SpringBoot] React + TypeScript + JPA 프로젝트 (8) - useContext 전역변수 다루기이전글https://greed-yb.tistory.com/310 [SpringBoot] React + TypeScript + JPA 프로젝트 (7) - 게시판 만들기(글

greed-yb.tistory.com

 

 

 

이번엔 게시글을 삭제해 보자

 

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 으로 

데이터 제어하는 쪽을 선호 하는 편이다

 

각자에 맞춰서 사용하자

댓글