본문 바로가기
React

[5] 페이지 이동 & 모달

by 민지기il 2025. 3. 7.

[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