이전글
https://greed-yb.tistory.com/321
게시판 목록과 작성 페이지를 만들었으니
이번엔 조회 페이지를 만들어보겠다
board.tsx - 게시판 목록
페이징 구현까지 한 게시글 목록에서
제목을 눌러서 해당 게시물로 들어가기 위해 <Link> 태그를 추가해준다
import React, {useEffect, useState} from "react";
import axios from "axios";
import {Link} from "react-router-dom";
import {Paging} from "../utils/pagination";
import {useUserContext} from "../utils/UserInfo";
// 게시판 변수 타입 설정
interface Board {
no: number;
title: string;
writer: string;
created: string;
}
function Board() {
// 게시판 목록 변수
const [BoardList, setBoardList] = useState<Board[]>([]);
const [boardDataSet, setBoardDataSet] = useState<Board[]>([]);
// 검색 변수
const [searchOption, setSearchOption] = useState('title');
const [search, setSearch] = useState('');
// enter key 적용
const enterKey = (event: React.KeyboardEvent<HTMLInputElement>) =>{
if(event.key === "Enter"){
axiosBoardList();
}
}
useEffect(() => {
axiosBoardList();
}, []);
// 게시물 목록 가져오는 axios
const axiosBoardList = async () => {
await axios.get("/api/boardList" , {
params: {
searchOption,
search,
},
}).then((response) => {
if(response.data.length > 0){
setBoardList(response.data);
setPage(1);
}else{
// 게시글 변수 초기화
setBoardList([]);
setBoardDataSet([]);
setPage(1);
}
}).catch((error) => {
console.log("error : " + error);
});
}
// 현재 페이지 설정
const [page, setPage] = useState(0);
const handlePageChange = (page: number) => {
setPage(page);
};
// 한 페이지에서 나오는 게시글 수
const pageItemSize : number = 5;
// pageItemSize 을 가지고 배열을 만들어준다
let indexArray = Array.from({ length: pageItemSize }, (item, index) => {
return index;
});
let pageIndex : number[] = [];
// 현재 페이지 번호에 맞는 게시글 번호를 담는다
// pageItemSize = 5 로 설정한 상태
// 1page = [0,1,2,3,4] / 2page = [5,6,7,8,9] ....
pageIndex = page === 1 ? indexArray : indexArray.map((item) => item + (page - 1) * pageItemSize);
// 현재 페이지의 게시글 데이터
const pagingData: Board[] = [];
// 검색 및 페이징에 따른 데이터 변경
const dataChange = () => {
if(BoardList.length > 0){
for (let i = 0; i < indexArray.length; i++) {
if (BoardList && BoardList[pageIndex[i]] === undefined) {
break;
} else {
pagingData.push(BoardList[pageIndex[i]]);
}
}
setBoardDataSet(pagingData);
}else{
setBoardDataSet([]);
}
};
// 게시물 변수 및 페이지 클릭 시 게시글 데이터 변경
useEffect(() => {
if (BoardList.length > 0) {
dataChange();
}
}, [BoardList ,page]);
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">Tables</li>
<li className="breadcrumb-item active">Data</li>
</ol>
</nav>
</div>
<section className="section">
<div className="row">
<div className="col-lg-12">
<div className="card">
<div className="card-body">
<h5 className="card-title">게시판 목록</h5>
<Link to="/boardWrite" className="btn btn-dark" type="button" style={{float:"right"}}>글 작성</Link>
<table className="table datatable">
<thead>
<tr>
<th>
No
</th>
<th>Title</th>
<th>Writer</th>
<th data-type="date" data-format="YYYY/DD/MM">Created</th>
</tr>
</thead>
<tbody>
{boardDataSet.length > 0 ? (
boardDataSet.map((board) => (
<tr key={board.no}>
<td>{board.no}</td>
<td><Link to={`/BoardRead/${board.no}`} >{board.title}</Link></td>
<td>{board.writer}</td>
<td>{board.created}</td>
</tr>
))
) : (
<tr>
<td colSpan={4} style={{ textAlign: 'center' }}>데이터가 없습니다.</td>
</tr>
)}
</tbody>
</table>
<div className="search-bar" style={{textAlign: "center"}}>
<select onChange={(event => setSearchOption(event.target.value))}>
<option value="title">제목</option>
<option value="writer">작성자</option>
</select>
<input type="text" name="query" placeholder="Search" title="Enter search keyword" onChange={event => setSearch(event.target.value)} onKeyDown={enterKey}/>
<button type="button" title="Search" className="btn btn-light" onClick={axiosBoardList}>검색</button>
<Paging
currentPage={page}
pageItemSize={pageItemSize}
totalData={BoardList.length}
clickPage={handlePageChange}
pageSize={10}
/>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
);
}
export default Board;
App.tsx
Route path 에 게시글 no 를 인자로 받는다
여기는 소스를 첨부하지 않겠다
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();
// Route path 로 받은 no 값을 받아온다
const { no } = useParams();
const [board, setBoarRead] = useState<BoardRead>({
title: "",
contents: "",
writer: ""
});
useEffect(() => {
axios.get("/api/boardRead" , {
params : {
no
}
}).then((response => {
setBoarRead(response.data);
})).catch((error) => {
console.log(error)
})
}, [no])
const [data, setData] = useState<BoardRead[]>([]);
// 태그의 name 을 인식해서 값을 가져온다
const dataCheck = (event: { target: { value: string; name: string; }; }) => {
const { value, name } = event.target;
setData({
...data,
[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" , data ,{
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} />
</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}/>
</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}></textarea>
</div>
<div className="text-center">
<button type="button" className="btn btn-primary" onClick={axiosSave}>저장</button>
<Link to="/board" className="btn btn-secondary">뒤로</Link>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
</main>
);
}
export default BoardRead;
게시판 목록에서 클릭한 글번호를 useParams() 으로 받아와서
axios 를 이용하여 글을 조회한다
태그에는 value 에 데이터를 바인딩해 준다
현재 태그에 readOnly 설정을 하지 않았는데도 글이 작성되지 않을 것이다
그 이유는 불러온 값은 setBoardRead() 을 이용하여 board 에 담았는데
태그 필드에서 onChange 는 data 를 바라보고 있기 때문이다
해당 소스는 게시글 수정을 위해 소스를 변경할 것이고
태그에 readOnly 설정을 하는 방식은 아래 처럼 하면 된다
<input type="text" Id="title" name="title" value={board.title} onChange={dataCheck} readOnly={true} />
Controller
// 게시글 조회하기
@GetMapping("/boardRead")
public Board boardRead(@RequestParam Long no) throws Exception{
System.err.println("no : " + no);
// Optional<Board> vo = boardRepository.findById(no);
// return vo.get();
// 이런방식도 있다
return boardRepository.findById(no).orElseGet(()-> {
Board result = new Board();
result.setNo(no);
result.setTitle("오류가 발생하였습니다");
result.setWriter("오류가 발생하였습니다");
result.setContents("오류가 발생하였습니다");
return result;
});
}
findById 는 JPA 기본 메서드로 설명을 생략하겠다
TEST
데이터가 정상적으로 조회되었다
이제 전역변수를 이용하여 수정, 삭제를 제어하고 기능을 추가하면 된다
댓글