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;
이렇게 변경했습니다.
먼저 기존의 게시판 코드를 살펴보면 다음과 같은 특징이 있습니다:
여러 개의 상태 관리
boardData
totalPages
currentPage
useEffect를 사용한 데이터 fetch
useEffect(() => {
fetchBoardData();
}, [currentPage]);
수동적인 로딩/에러 상태 관리
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([]);
}
};
이러한 방식은 다음과 같은 문제점을 가지고 있습니다:
React Query를 사용하여 위의 문제점들을 해결해보겠습니다.
먼저 필요한 패키지를 설치합니다:
npm install @tanstack/react-query
# or
yarn add @tanstack/react-query
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,
}),
});
// ...
}
boardData
와 totalPages
상태 제거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,
}),
자동 캐싱
상태 관리 간소화
자동화된 상태 처리
데이터 동기화
React Query를 도입함으로써 다음과 같은 이점을 얻을 수 있었습니다:
이러한 장점들로 인해 React Query는 데이터 fetch가 필요한 React 애플리케이션에서 매우 유용한 도구가 될 수 있습니다.
실제 프로덕션 환경에서는 다음과 같은 추가 설정을 고려해볼 수 있습니다: