본문 바로가기
개발/Spring

[SpringBoot] React + TypeScript + JPA 프로젝트 (10) - 게시판 만들기(수정하기)

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

이전글

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("저장 실패하였습니다.");
            })
        }
    }

    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>
                                        <Link to="/board" className="btn btn-secondary">뒤로</Link>
                                    </div>
                                </form>

                            </div>
                        </div>

                    </div>
                </div>
            </section>
        </main>

    );
}

export default BoardRead;

 

 

 

 

로그인 또는 Jwt Token 을 이용하여 전역변수에 저장한 유저정보를 이용한다

 

 

전역변수에 따라 readOnly 을 설정하기 위한 변수 선언

전역변수에 따라 수정버튼의 display 를 설정하기 위한 변수 선언

 

하나에서 readOnly 와 display 를 설정해도 되는데 

기능이 다르다보니 나눠놓았다

 

 

 

조회한 데이터의 글쓴이와 현재 로그인한 사용자가 같을 경우

 

preState예전 데이터를 유지하기 위해 사용한다

변수명은 사용자가 원하는 대로 하면 된다

 

아래 title, writer, contents 를 설정함으로써

이전 데이터는 유지하면서

title, writer, contents 의 값을 변경하겠다 라는 의미인데

readOnly 에 변수를 3개만 생성하였으니 사실상 전체를 변경하는 거다

 

기존 데이터는 유지하면서 설정한 데이터의 값만 변경한다 라고 기억하자

 

 

 

 

title 과 contents 수정 시

 

setBoardRead 에 현재 작성한 내용을 변경해 준다

 

 

 

 

글을 저장할 때 데이터를 담은 변수명이 변경되었으므로 변수명만 변경해 준다

 

 

 

 

 

readOnly 속성에 readOnly 변수를 넣어 제어를 하고 있고

style 속성은 displayButton 변수를 삼항연산자를 사용하여 제어하고 있다

 

 

 

 

Controller
@PostMapping("/boardSave")
public String boardSave(@RequestBody Board vo, @RequestParam String checkToken) throws Exception{
    try {
        // refreshToken 에서 사용자의 username 을 가져온다
        String username = jwtTokenUtil.getUsernameFromRefreshToken(checkToken);

        // 수정이 아닌 글 작성시 글쓴이 데이터가 없기 때문에
        if(vo.getWriter() == null){
            vo.setWriter(username);
        }

        boardRepository.save(vo);
        return "success";
    }catch (Exception e){
        e.printStackTrace();
        return "유효하지 않은 사용자";
    }
}

 

 

 

 

 

 

 

TEST

다른 부분은 수정이 되지만 글쓴이는 readOnly 가 적용되고 있다

 

 

 

 

수정되었다

 

 

 

 

 

 

다른 사용자 계정으로 접속해 본다

 

 

저장 버튼이 안 보이고 있다

 

 

 

 


 

작성자가 페이지에 들어오면 바로 수정이 되는 게 아니라

수정 버튼을 클릭하면 수정 페이지로 변경되거나 

readOnly 설정이 해제가 되는 식으로 되는 게 보통이지만

지금 구현된 코드로 다 변경이 가능한 기능이므로 생략하였다

 

 

댓글