[1] 페이지 이동 component
serverData.prev가 있으면 이전 페이지로 이동, pageNumList를 가운데에 배치, serverData.nex로 다음 페이지 이동
import React from 'react'
function PageComponent ({ serverData, movePage }) {
return (
<div className="m-6 flex justify-center">
{serverData.prev ? (
<div
className="m-2 p-2 w-16 text-center font-bold text-blue-400"
onClick={() => movePage({ page: serverData.prevPage })}
>
Prev
</div>
) : (
<></>
)}
{serverData.pageNumList.map(pageNum => (
<div
key={pageNum}
className={`
m-2 p-2 w-12 text-center rounded shadow-md text-white
${serverData.current === pageNum ? 'bg-gray-500' : 'bg-blue-400'}
`}
onClick={() => movePage({ page: pageNum })}
>
{pageNum}
</div>
))}
{serverData.next ? (
<div
className="m-2 p-2 w-16 text-center font-bold text-blue-400"
onClick={() => movePage({ page: serverData.nextPage })}
>
Next
</div>
) : (
<></>
)}
</div>
);
};
export default PageComponent;
function ListComponent() {
const {page, size, moveToList} = useCustomMove()
const[serverData, setServerData] = useState(initState)
useEffect(()=>{
getList({page,size}).then(data=>{
console.log(data)
setServerData(data)
})
},[page,size]);
return (
<div className="border-2 border-blue-100 mt-10 mr-2 ml-2">
<div className="flex flex-wrap mx-auto justify-center p-6">
{serverData.dtoList.map(todo => (
....(생략)
</div>
))}
</div>
<PageComponent serverData={serverData} movePage={moveToList}></PageComponent>
</div>
);
}
export default ListComponent
상단의 PageComponent를 ListComponent에 가져와서 serverData, moveToList를 넣어준다.
moveToList는 커스텀훅에 만들어둔 것 (이전 글 참고)
[2] 동일한 page를 클릭해도 서버 호출하기
function ListComponent() {
const {page, size, refresh, moveToList} = useCustomMove()
const[serverData, setServerData] = useState(initState)
useEffect(()=>{
getList({page,size}).then(data=>{
console.log(data)
setServerData(data)
})
},[page,size, refresh]);
refresh 추가하기
//useCustomMove
const useCustomMove = () => {
const navigate = useNavigate()
const [refresh, setRefresh] = useState(false)
... (생략)
const moveToList =(pageParam) => {
navigate({pathname:`../list`, search:queryDefault})
let queryStr = ""
if (pageParam){
const pageNum = getNum(pageParam.page, 1)
const sizeNum = getNum(pageParam.size, 10)
queryStr = createSearchParams({page:pageNum, size:sizeNum}).toString()
}else{
queryStr = queryDefault
}
setRefresh(!refresh) refresh 값이 다른 값으로 바뀜
navigate({pathname: `../list`, search:queryStr})
}
... (생략)
return {moveToList, moveToModify, moveToRead, page, size, refresh} refresh 값 추가
}
[2] 조회 페이지 이동
const useCustomMove = () => {
const navigate = useNavigate()
const [refresh, setRefresh] = useState(false)
...(생략)
const moveToRead = (num) =>{
navigate({
pathname: `../read/${num}`,
search: queryDefault
})
}
return {moveToList, moveToModify, moveToRead, page, size, refresh}
}
export default useCustomMove;
function ListComponent() {
const {page, size, refresh, moveToList, moveToRead} = useCustomMove()
const[serverData, setServerData] = useState(initState)
...(생략)
return (
<div className="border-2 border-blue-100 mt-10 mr-2 ml-2">
<div className="flex flex-wrap mx-auto justify-center p-6">
{serverData.dtoList.map(todo => (
<div key={todo.tno}
className="w-full min-w-[400px] p-2 m-2 rounded shadow-md"
onClick={() => moveToRead(todo.tno)} 버튼 클릭 시 목록 조회하도록
>
<div className="flex">
<div className="font-extrabold text-2xl p-2 w-1/12">
{todo.tno}
[3] 게시글 등록 기능
1) api 개발
export const postAdd = async (todoObj) => {
//json.stringify(obj) => json 문자열
const res = await axios.post(`${prefix}/`, todoObj)
return res.data
}
json.stringify으로 객체를 JSON 형식의 문자열로 변환했어야 하는데
axios.post()가 자동으로 객체 -> json 문자열로 변환해서 전송함
const todoObj = {
tno: 1,
title: "할 일 1",
complete: false
};
console.log(todoObj); // 자바스크립트 객체, 결과: { tno: 1, title: "할 일 1", complete: false }
console.log(JSON.stringify(todoObj)); // JSON 문자열로 변환, 결과: '{"tno":1,"title":"할 일 1","complete":false}'
2) component 추가
import React from 'react'
function AddComponent(props) {
return (
<div> AddComponent
</div>
)
}
export default AddComponent
3) AddPage에 추가
import React from 'react';
import AddComponent from '../../components/todo/AddComponent';
function AddPage(props) {
return (
<div className="p-4 w-full bg-white">
<div className={'text-3xl font-extrabold'}>
Todo AddPage
</div>
<AddComponent/> 추가하기
</div>
);
}
export default AddPage;
[4] 새로운 Todo 데이터 저장하기 위해 입력 폼 생성
import React, { useState } from 'react'
const initState ={
title:'',
writer:'',
dueDate: ''
}
function AddComponent(props) {
const[todo, setTodo] = useState({...initState})
return (
<div> Add Component
</div>
)
}
export default AddComponent
todo → 현재 저장된 데이터 (초기값은 initState)
setTodo → todo 값을 바꿀 때 사용하는 함수
useState({...initState}) → 초기 상태를 initState로 설정
[5] 모달
server-side rendering: POST로 전송해서 redirect 시켜서 GET 방식으로 확인함 (PRG 방식) :
client가 post로 서버 호출 -> 서버에서 HTTP 헤더 전송 (location) -> GET
요청 -> modal 창 띄우기 -> 창 닫으면 이동
[5-1] ResultModal.js
import React from 'react'
const ResultModal = ({ title, content, callbackFn }) => {
return (
<div
className="fixed top-0 left-0 z-[1055] flex h-full w-full justify-center bg-black bg-opacity-20"
onClick={() => {
if (callbackFn) {
callbackFn();
}
}}
>
<div className="absolute bg-white shadow dark:bg-gray-700 opacity-100 w-1/4 rounded mt-10 mb-10 px-6 min-w-[600px]">
<div className="justify-center bg-warning-400 mt-6 mb-6 text-2xl border-b-4 border-gray-500">
{title}
</div>
<div className="text-4xl border-orange-400 border-b-4 pt-4 pb-4">
{content}
</div>
<div className="justify-end flex">
<button
className="rounded bg-blue-500 mt-4 mb-4 px-6 pt-4 pb-4 text-lg text-white"
onClick={() => {
if (callbackFn) {
callbackFn();
}
}}
>
Close Modal
</button>
</div>
</div>
</div>
);
};
export default ResultModal;
[5-2] AddComponent.js
import React, { useState } from 'react';
import ResultModal from '../common/ResultModal';
import useCustomMove from '../../hooks/useCustomMove';
const initState = {
title: '',
writer: '',
dueDate: '',
};
function AddComponent(props) {
const [todo, setTodo] = useState({ ...initState });
const [result, setResult] = useSate(null)
const {moveToList} = useCustomMove()
const handleChangeTodo = (e) => {
console.log(e.target.name, e.target.value);
todo[e.target.name] = e.target.value; // 변경됨
setTodo({ ...todo });
};
const handleClickAdd = () => {
//console.log(todo);
postAdd(todo).then(result => {
//{TNO: 104} 형태임
setResult(result.TNO)
setTodo({...initState})
})
};
const closeModal = () => {
setResult(null)
moveToList()
}
return (
<div className="border-2 border-sky-200 mt-10 m-2 p-4">
<div className="flex justify-center">
<div className="relative mb-4 flex w-full flex-wrap items-stretch">
<div className="w-1/5 p-6 text-right font-bold">TITLE</div>
<input
className="w-4/5 p-6 rounded-r border border-solid border-neutral-500 shadow-md"
name="title"
type="text"
value={todo.title}
onChange={handleChangeTodo}
/>
</div>
</div>
<div className="flex justify-center">
<div className="relative mb-4 flex w-full flex-wrap items-stretch">
<div className="w-1/5 p-6 text-right font-bold">WRITER</div>
<input
className="w-4/5 p-6 rounded-r border border-solid border-neutral-500 shadow-md"
name="writer"
type="text"
value={todo.writer}
onChange={handleChangeTodo}
/>
</div>
</div>
<div className="flex justify-center">
<div className="relative mb-4 flex w-full flex-wrap items-stretch">
<div className="w-1/5 p-6 text-right font-bold">DUEDATE</div>
<input
className="w-4/5 p-6 rounded-r border border-solid border-neutral-500 shadow-md"
name="dueDate"
type="date"
value={todo.dueDate}
onChange={handleChangeTodo}
/>
</div>
</div>
<div className="flex justify-end">
<div className="relative mb-4 flex p-4 flex-wrap items-stretch">
<button
type="button"
onClick={handleClickAdd}
className="rounded p-4 w-36 bg-blue-500 text-xl text-white"
>
ADD
</button>
</div>
</div>
{result ? <ResultModal
title={'Add result'}
content = {`New ${result} Added`}
callbackFn={closeModal}
/>
:
<></> }
</div>
);
}
export default AddComponent;
[5-3] useCustomMove.js
import { createSearchParams, useNavigate, useSearchParams } from 'react-router-dom';
const getNum = (param, defaultValue)=>{
if(!param){
return defaultValue
}
return parseInt(param)
}
const useCustomMove = () => {
const navigate = useNavigate()
const [refresh, setRefresh] = useState(false)
const [queryParams] = useSearchParams()
const page = getNum(queryParams.get('page'),1)
const size = getNum(queryParams.get('size'),10)
//page=3&size=10 만들기
const queryDefault = createSearchParams({page,size}.toString())
const moveToList =(pageParam) => {
navigate({pathname:`../list`, search:queryDefault})
let queryStr = ""
if (pageParam){
const pageNum = getNum(pageParam.page, 1)
const sizeNum = getNum(pageParam.size, 10)
queryStr = createSearchParams({page:pageNum, size:sizeNum}).toString()
}else{
queryStr = queryDefault
}
setRefresh(!refresh)
navigate({pathname: `../list`, search:queryStr})
}
const moveToModify = (num) => {
navigate({
pathname: `../modify/${num}`,
search: queryDefault
})
}
const moveToRead = (num) =>{
navigate({
pathname: `../read/${num}`,
search: queryDefault
})
}
return {moveToList, moveToModify, moveToRead, page, size, refresh}
}
export default useCustomMove;
'React' 카테고리의 다른 글
[4] customHook (0) | 2025.03.06 |
---|---|
[3] useParams와 useNavigate (0) | 2025.03.06 |
[2] 공동 Layout 동작 (0) | 2025.03.06 |
[1] ReactRouter 설정 (0) | 2025.03.06 |