관리 메뉴

거니의 velog

(12) 리액트와 API 서버 통신 2 본문

SpringBoot_React 풀스택 프로젝트

(12) 리액트와 API 서버 통신 2

Unlimited00 2024. 2. 28. 18:49

4. 네비게이션 관련 커스텀 훅

* ReadComponent가 완성되기 위해서 남은 작업은 다시 목록 화면으로 이동하는 기능이 추가되어야 한다. 리액트에서 화면의 구성은 컴포넌트를 이용해서 처리할 수 있지만, 컴포넌트들 내부에서 만들어지는 공통적인 코드의 경우 커스텀 훅(custom hook)을 이용해서 작성한다.

* 예제의 경우 조회 화면에서는 다시 목록 화면으로 이동해야 하는 기능이 필요하고 이를 구현하기 위해서 useNavigate()나 useSearchParams를 이용해야만 한다. 이렇게 만들어진 기능은 등록이나 수정/삭제 화면에서도 동일하게 사용된다.


(1) 목록 페이지로 이동

* 커스텀 훅을 제작하기 위해서 프로젝트의 src 폴더에 hooks 폴더를 추가하고 useCustomMove.js 파일을 추가한다(커스텀 훅은 반드시 'use-' 접두사로 시작해야 한다).

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 });
  };

  return { moveToList, page, size };
};

export default useCustomMove;

* useCustomMove()의 내부에는 useNavigate() 와 useSearchParams() 를 이용해서 원하는 기능을 moveToList() 로 만들고 이를 page, size 와 함께 반환한다. 외부에서 useCustomMove()를 이용하면 이전에 비해서 간단히 useNavigate() 를 이용하게 되고 반환된 데이터들 중에서 필요한 데이터만 선별해서 사용할 수 있다.

* ReadComponent에서 useCustomMove()의 실행 결과 중에서 moveToList()를 이용하고 버튼을 추가해 보자.

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

(...)

const ReadComponent = ({ tno }) => {
  const [todo, setTodo] = useState(initState);
  const { moveToList } = 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("Tno", todo.tno)}
      {makeDiv("Writer", todo.writer)}
      {makeDiv("Title", todo.title)}
      {makeDiv("Due Date", todo.dueDate)}
      {makeDiv("Complete", todo.complete ? "Completed" : "Not Yet")}

      {/* buttons starts... */}
      <div className="flex justify-end p-4">
        <button
          type="button"
          className="rounded p-4 m-2 text-xl w-32 text-white bg-blue-500"
          onClick={() => moveToList()}
        >
          List
        </button>
      </div>
    </div>
  );
};

export default ReadComponent;

* 화면에서 ReadComponent는 하단 오른쪽에 'List' 버튼이 생성되고 클릭 시에 '/todo/list' 로 이동하게 된다. '/todo/list'로 이동할 때 'page=1&size=10'이 자동으로 처리된다.


(2) 수정/삭제 페이지로 이동

* 커스텀 훅을 이용하면 여러 컴포넌트들이 필요한 기능을 하나의 함수로 묶어서 처리할 수 있다는 장점이 있다. useCustomMove의 기능을 조금 더 확장해서 수정/삭제로 이동할 수 있는 기능을 추가해 본다.

import {
  createSearchParams,
  useNavigate,
  useSearchParams,
} from "react-router-dom";

const getNum = (param, defaultValue) => {
  (...)
};

const useCustomMove = () => {
  (...)

  const moveToList = (pageParam) => {
    (...)
  };

  const moveToModify = (num) => {
    console.log(queryDefault);

    navigate({
      pathname: `../modify/${num}`,
      search: queryDefault, //수정시에 기존의 쿼리 스트링 유지를 위해
    });
  };

  return { moveToList, moveToModify, page, size }; // moveToModify 추가
};

export default useCustomMove;

* useCustomMove 에는 moveToModify() 를 구현하고 외부에서 사용 가능하도록 return 구문에 추가해 준다. 이를 ReadComponent 에서는 수정/삭제로 이동할 수 있는 버튼을 추가해서 사용한다.

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

(...)

const ReadComponent = ({ tno }) => {
  const [todo, setTodo] = useState(initState);
  // 이동과 관련된 기능은 모두 useCustomMove() 로
  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("Tno", todo.tno)}
      {makeDiv("Writer", todo.writer)}
      {makeDiv("Title", todo.title)}
      {makeDiv("Due Date", todo.dueDate)}
      {makeDiv("Complete", todo.complete ? "Completed" : "Not Yet")}

      {/* buttons starts... */}
      <div className="flex justify-end p-4">
        <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(tno)}
        >
          Modify
        </button>
      </div>
    </div>
  );
};

export default ReadComponent;

* 변경된 ReadComponent는 useCustomMove()의 결과인 movoToModify()를 이용할 수 있으므로, 적은 양의 코드만으로도 원하는 기능들을 처리할 수 있게 되었다. 브라우저의 조회 화면에서 'Modify' 버튼을 클릭해서 이동하는 것을 확인해 보자.

* 이전 장의 경우 조회 페이지로 이동하는 기능은 pages/todo/ReadPage에서 처리되었지만, ReadComponent가 필요한 기능을 useCustomMove를 이용해서 처리할 수 있으므로 기존의 useNavigate 를 이용하는 코드를 삭제할 수 있다.

import React from "react";
import { useParams } from "react-router-dom";
import ReadComponent from "../../components/todo/ReadComponent";

const ReadPage = () => {
  const { tno } = useParams();

  return (
    /* text-3xl 삭제 */
    <div className="font-extrabold w-full bg-white mt-6">
      <div className="text-2xl">Todo Read Page Component {tno}</div>
      <ReadComponent tno={tno}></ReadComponent>
    </div>
  );
};

export default ReadPage;

* 코드가 훨씬 더 간결해 진 모습이다.