React/실습 정리

React 7강 - Todo 읽기(Read)·목록(List) 컴포넌트 & 커스텀 훅 구현

코딩하는냥이 2025. 7. 31. 09:47
반응형

이번 글에서는 서버에서 Todo 데이터를 읽어서 보여주는 컴포넌트(ReadComponent, ListComponent)
그리고 페이지 이동/쿼리 파라미터 처리를 쉽게 해주는 커스텀 훅(useCustomMove) 구현 방법을 배우겠습니다.


1. Todo 상세 조회 컴포넌트(ReadComponent.js) 생성

src/components/ReadComponent.js

import { useEffect, useState } from "react"
import { getOne } from "../../api/todoApi"
import useCustomMove from "../../hooks/useCustomMove"

const initState = {
    tno: 0,
    title: '',
    writer: '',
    dueDate: null,
    complete: false
}

const makeDiv = (title, value) =>
    <div className="flex justify-center">
        <div className="relative mb-4 w-full flex flex-wrap items-stretch text-lg">
            <div className="w-1/5 p-6 text-right font-bold">{title}</div>
            <div className="w-4/5 p-6 rounded-r border border-solid shadow-md">{value}</div>
        </div>
    </div>

const ReadComponent = ({ tno }) => {
    const [todo, setTodo] = useState(initState)
    const {moveToList, moveToModify} = useCustomMove()

    useEffect(() => {
        getOne(tno).then(data => {
            console.log(data)
            setTodo(data)
        })
    }, [tno])

    return (
        <div className="border-2 border-sky-200 mt-10 m-2 p-4">
            {makeDiv('번호', todo.tno)}
            {makeDiv('작성자', todo.writer)}
            {makeDiv('제목', todo.title)}
            {makeDiv('작성일자', todo.dueDate)}
            {makeDiv('수정', todo.complete ? '수정':'아니요')}

            <div className="flex justify-end">
                <button type="button" className="rounded p-4 m-2 text-xl w-32 text-white bg-blue-500" onClick={()=>moveToList()}>List</button>
                <button type="button" className="rounded p-4 m-2 text-xl w-32 text-white bg-red-500" onClick={()=>moveToModify(todo.tno)}>Modify</button>
            </div>
        </div>
    )
}

export default ReadComponent;

2. 커스텀 훅(useCustomMove.js) 구현

src/hooks/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 [queryParams] = useSearchParams()

    const page = getNum(queryParams.get("page"), 1)
    const size = getNum(queryParams.get("size"), 10)
    const queryDefault = createSearchParams({ page, size }).toString()

    const moveToList = (pageParam) => {
        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
        }
        navigate({pathname:'../list', search:queryStr})
    }
    const moveToModify = (num) => {
        navigate({
            pathname:`../modify/${num}`,
            search:queryDefault
        })
    }
    return {moveToList, page, size, moveToModify}
}

export default useCustomMove;
  • 설명:
    • 현재 URL 쿼리(page, size)값을 자동으로 추출해서,
      리스트/수정 페이지 이동 시 쿼리스트링이 항상 유지되게 처리합니다.

3. ReadPage에서 컴포넌트 적용

src/pages/todo/ReadPage.js
(중요 부분만)

import ReadComponent from "../../components/ReadComponent";

const ReadPage = ({ tno }) => {
  return (
    <div>
      <ReadComponent tno={tno} />
    </div>
  );
};

4. Todo 목록 컴포넌트(ListComponent.js) 생성

src/components/ListComponent.js

import { useEffect, useState } from "react";
import useCustomMove from "../../hooks/useCustomMove";
import { getList } from "../../api/todoApi";

const initState = {
    dtoList:[],
    pageNumList:[],
    pageRequestDTO:null,
    prev:false,
    next:false,
    totalCount:0,
    prevPage:0,
    nextPage:0,
    totalPage:0,
    current:0
}

const ListComponent = () => {
    const {page, size} = useCustomMove()
    const [serverData, setServerData] = useState(initState)

    useEffect(() => {
        getList({page, size}).then( 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 key={todo.tno} className="w-full min-w-[400px] p-2 m-2 rounded shadow-md">
                        <div className="flex">
                            <div className="font-extrabold text-2xl p-2 w-1/12">{todo.tno}</div>
                            <div className="font-extrabold text-xl m-1 p-2 w-8/12">{todo.writer}</div>
                            <div className="font-medium text-xl m-1 p-2 w-3/12">{todo.dueDate}</div>
                        </div>
                    </div>
                )}
            </div>
        </div>
    )
}

export default ListComponent;

💡 포인트 정리

  • useCustomMove 훅을 사용하면,
    페이징 쿼리스트링이 자동으로 관리되어
    페이지 간 이동이나 수정 시 값이 보존됨
  • ReadComponent / ListComponent에서
    API 데이터 받아와서 상태관리 & 화면 출력
  • 코드 분리로 컴포넌트/훅 재사용성이 높아짐

📌 정리하자면, 이번 강의에서는 서버에서 받아온 Todo 데이터로 상세/목록 페이지를 구현하고, 페이지 이동 및 쿼리 관리까지 쉽게 만드는 커스텀 훅의 활용법을 실습했습니다.