알고는 가자 - change to react - query

BackEnd_Ash.log·2024년 12월 7일
0

universe

목록 보기
4/5
import React, { useEffect, useState } from 'react';
import {
  Pagination,
  PaginationContainer,
  Table, TableBody,
  TableContainer, TableHead, WriteButton,
} from '@/styles/pages/board/BoardPage.styled';
import { useRouter } from 'next/router';
import { findBoardList } from '@/api/board.api';
import BoardList from '@/components/Board/BoardList/BoardList';

interface BoardData {
  id: number;
  createdAt: string;
  title: string;
  content: string;
  imgUrl: string;
  likeCount: number;
  commentCount: number;
  category: string;
  categoryId: number;
  nickname: string;
  email: string;
}

const Index = () => {
  const router = useRouter();
  const [currentPage, setCurrentPage] = useState(1);
  const [boardData, setBoardData] = useState<BoardData[] | null>(null); // 초기값을 null로 설정
  const [totalPages, setTotalPages] = useState(1); // 총 페이지 수
  const itemsPerPage = 10;

  const fetchBoardData = async () => {
    try {
      const response = await findBoardList(currentPage, itemsPerPage, 'DESC');
      setBoardData(response.data.data);
      setTotalPages(response.data.totalPages);
    } catch (error) {
      console.error('Failed to fetch board data:', error);
      setBoardData([]); // 에러 발생 시 빈 배열로 설정
    }
  };

  const handlePageChange = (page: number) => {
    setCurrentPage(page);
  };

  const moveWritePage = () => {
    router.push('/board/write');
  };

  const moveToDetailPage = (id: number) => {
    router.push(`/board/${id}`);
  };

  const renderPagination = () => {
    const pages = [];

    for (let i = 1; i <= totalPages; i++) {
      pages.push(
        <button
          key={i}
          className={i === currentPage ? 'active' : ''}
          onClick={() => handlePageChange(i)}
        >
          {i}
        </button>
      );
    }

    return pages;
  };

  useEffect(() => {
    fetchBoardData();
  }, [currentPage]);

  return (
    <TableContainer>
      <Table>
        <TableHead>
          <tr>
            <th>번호</th>
            <th>카테고리</th>
            <th>제목</th>
            <th>글쓴이</th>
            <th>날짜</th>
          </tr>
        </TableHead>
        <TableBody>
          <BoardList boardData={boardData} moveToDetailPage={moveToDetailPage} />
        </TableBody>
      </Table>
      <PaginationContainer>
        <Pagination>{renderPagination()}</Pagination>
        <WriteButton onClick={moveWritePage}>글쓰기</WriteButton>
      </PaginationContainer>
    </TableContainer>
  );
};

export default Index;

위의 코드를 react-query 를 적용시켜서 리팩토링 하게 되면 ,

import React from 'react';
import {
  Pagination,
  PaginationContainer,
  Table,
  TableBody,
  TableContainer,
  TableHead,
  WriteButton,
} from '@/styles/pages/board/BoardPage.styled';
import { useRouter } from 'next/router';
import { findBoardList } from '@/api/board.api';
import BoardList from '@/components/Board/BoardList/BoardList';
import { useQuery } from '@tanstack/react-query';

interface BoardData {
  id: number;
  createdAt: string;
  title: string;
  content: string;
  imgUrl: string;
  likeCount: number;
  commentCount: number;
  category: string;
  categoryId: number;
  nickname: string;
  email: string;
}

const Index = () => {
  const router = useRouter();
  const [currentPage, setCurrentPage] = React.useState(1);
  const itemsPerPage = 10;

  // React Query 적용
  const {
    data: boardResponse,
    isLoading,
    isError,
  } = useQuery({
    queryKey: ['boards', currentPage, itemsPerPage],
    queryFn: () => findBoardList(currentPage, itemsPerPage, 'DESC'),
    select: (response) => ({
      data: response.data.data as BoardData[],
      totalPages: response.data.totalPages as number,
    }),
  });

  const handlePageChange = (page: number) => {
    setCurrentPage(page);
  };

  const moveWritePage = () => {
    router.push('/board/write');
  };

  const moveToDetailPage = (id: number) => {
    router.push(`/board/${id}`);
  };

  const renderPagination = () => {
    if (!boardResponse) return null;

    const pages = [];
    for (let i = 1; i <= boardResponse.totalPages; i++) {
      pages.push(
        <button
          key={i}
          className={i === currentPage ? 'active' : ''}
          onClick={() => handlePageChange(i)}
        >
          {i}
        </button>
      );
    }
    return pages;
  };

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (isError) {
    return <div>Error loading board data</div>;
  }

  return (
    <TableContainer>
      <Table>
        <TableHead>
          <tr>
            <th>번호</th>
            <th>카테고리</th>
            <th>제목</th>
            <th>글쓴이</th>
            <th>날짜</th>
          </tr>
        </TableHead>
        <TableBody>
          <BoardList
            boardData={boardResponse?.data || null}
            moveToDetailPage={moveToDetailPage}
          />
        </TableBody>
      </Table>
      <PaginationContainer>
        <Pagination>{renderPagination()}</Pagination>
        <WriteButton onClick={moveWritePage}>글쓰기</WriteButton>
      </PaginationContainer>
    </TableContainer>
  );
};

export default Index;

이렇게 변경했습니다.

React Query로 게시판 리팩토링하기

기존 코드

먼저 기존의 게시판 코드를 살펴보면 다음과 같은 특징이 있습니다:

  1. 여러 개의 상태 관리

    • boardData
    • totalPages
    • currentPage
  2. useEffect를 사용한 데이터 fetch

    useEffect(() => {
      fetchBoardData();
    }, [currentPage]);
  3. 수동적인 로딩/에러 상태 관리

    const fetchBoardData = async () => {
      try {
        const response = await findBoardList(currentPage, itemsPerPage, 'DESC');
        setBoardData(response.data.data);
        setTotalPages(response.data.totalPages);
      } catch (error) {
        console.error('Failed to fetch board data:', error);
        setBoardData([]);
      }
    };

이러한 방식은 다음과 같은 문제점을 가지고 있습니다:

  • 캐싱 기능 부재로 인한 불필요한 API 호출
  • 복잡한 상태 관리
  • 수동적인 에러 처리
  • 데이터 재검증 어려움

React Query로 개선하기

React Query를 사용하여 위의 문제점들을 해결해보겠습니다.

1. React Query 설정

먼저 필요한 패키지를 설치합니다:

npm install @tanstack/react-query
# or
yarn add @tanstack/react-query

2. 코드 리팩토링

import { useQuery } from '@tanstack/react-query';

const Index = () => {
  const [currentPage, setCurrentPage] = React.useState(1);
  const itemsPerPage = 10;

  const {
    data: boardResponse,
    isLoading,
    isError,
  } = useQuery({
    queryKey: ['boards', currentPage, itemsPerPage],
    queryFn: () => findBoardList(currentPage, itemsPerPage, 'DESC'),
    select: (response) => ({
      data: response.data.data as BoardData[],
      totalPages: response.data.totalPages as number,
    }),
  });
  // ...
}

3. 주요 변경사항

상태 관리 간소화

  • boardDatatotalPages 상태 제거
  • React Query의 data 속성으로 통합 관리

자동 로딩/에러 처리

if (isLoading) {
  return <div>Loading...</div>;
}

if (isError) {
  return <div>Error loading board data</div>;
}

데이터 변환

select: (response) => ({
  data: response.data.data as BoardData[],
  totalPages: response.data.totalPages as number,
}),

개선된 점

  1. 자동 캐싱

    • 동일한 페이지 재방문 시 즉시 데이터 표시
    • 불필요한 API 호출 감소
  2. 상태 관리 간소화

    • 여러 상태를 하나의 Query로 통합
    • useEffect 제거로 코드 복잡도 감소
  3. 자동화된 상태 처리

    • 로딩 상태 자동 감지
    • 에러 상태 자동 감지와 처리
  4. 데이터 동기화

    • 백그라운드에서 자동 데이터 갱신
    • 최신 데이터 유지 용이

결론

React Query를 도입함으로써 다음과 같은 이점을 얻을 수 있었습니다:

  • 코드 복잡도 감소
  • 데이터 관리 효율성 증가
  • 사용자 경험 개선
  • 유지보수성 향상

이러한 장점들로 인해 React Query는 데이터 fetch가 필요한 React 애플리케이션에서 매우 유용한 도구가 될 수 있습니다.

참고 사항

실제 프로덕션 환경에서는 다음과 같은 추가 설정을 고려해볼 수 있습니다:

  • 재시도 옵션 설정
  • 캐시 시간 설정
  • 에러 바운더리 사용
  • 낙관적 업데이트 구현
profile
꾸준함이란 ... ?

0개의 댓글