이제는 api 통신을 해야할 차례이다.
우선 메인 페이지에 있는 설문 조사들부터 가져오기로 하였다.
요구사항에서는 무한스크롤로 구현하라고 되어있었다.
일단 axios를 이용한 api 함수부터 만들었다.
mbti와 limit, offset을 통해 필터링을 할 수 있으며
mbti가 undefined인 경우에는 전체 데이터가 나왔었다.
limit는 화면에 보여줄 개수로써 깔끔하게 10개로 하도록 했다.
import api from './base';
const getColorSurvey = async (mbti?: string, limit?: number, offset?: number) => {
try {
const response = await api.get('/api/color-surveys', {
params: {
mbti,
limit,
offset,
},
});
return response.data;
} catch (error) {
console.error('Error fetching users:', error);
throw error;
}
};
export default getColorSurvey;
이렇게 get 요청을 하는 api 함수를 만들었고
header 부분에 필터링을 할 수 있도록 mbti를 선택할 수 있는 기능을 만들었는데 이 데이터를 전역적으로 관리할 필요가 있었다.
따라서
import { create } from 'zustand';
interface FilterMbtiState {
filterMbti: string;
setFilterMbti: (mbti: string) => void;
}
// devtools를 사용하는 경우, 타입을 지정해줘야 합니다.
const useFilterMbtiStore = create<FilterMbtiState>((set) => ({
filterMbti: '',
setFilterMbti: (mbti: string) => set({ filterMbti: mbti }),
}));
export default useFilterMbtiStore;
저장 공간을 만들었으며 아래와 같이 onChange 함수로 값을 갱신하도록 만들어주었다.
import mbtiTypes from '../constant/mbti';
import useFilterMbtiStore from '../store/filterMbtiStore';
const Header = () => {
const { filterMbti, setFilterMbti } = useFilterMbtiStore();
const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
setFilterMbti(event.target.value);
};
return (
...
);
};
export default Header;
이제 무한스크롤을 만들어 줄 차례다. 무한스크롤은 내가 항상 써왔던 방식인 useInfinityQuery와 react-intersection-observer 라이브러리를 사용하기로 정하였다.
import { useEffect } from 'react';
import { useInfiniteQuery } from '@tanstack/react-query';
import { useInView } from 'react-intersection-observer';
import getColorSurvey from '../api/getColorSurvey';
import useFilterMbtiStore from '../store/filterMbtiStore';
import { ColorSurvey, ColorSurveyInfo } from '../types/colorSurvey';
const MbtiColorInfo = () => {
const { filterMbti } = useFilterMbtiStore();
const { data, fetchNextPage, hasNextPage, isFetching } = useInfiniteQuery<ColorSurvey>({
queryKey: ['getColorSurveys', filterMbti],
queryFn: ({ pageParam = 0 }: any) => getColorSurvey(filterMbti || undefined, 10, pageParam), // offset을 pageParam * 10으로 설정
getNextPageParam: (lastPage, allPages) => {
// 현재 페이지 수
const currentPage = allPages.length;
// 다음 페이지의 offset 계산 (10씩 더하기)
return currentPage < lastPage.count / 10 ? currentPage : undefined;
},
initialPageParam: 0, // 초기 페이지 파라미터 설정
});
const { ref, inView } = useInView({
threshold: 1.0,
});
useEffect(() => {
if (inView && hasNextPage) {
fetchNextPage();
}
}, [inView, fetchNextPage, hasNextPage]);
return (
<div className="flex w-[100%] flex-col items-start self-stretch">
...
<ul className="flex flex-col w-[100%]">
{data?.pages.map((page) =>
page.results.map((item: ColorSurveyInfo) => (
...
</ul>
<div ref={ref} /> {/* 이 div가 뷰포트에 들어오면 다음 페이지를 불러옵니다 */}
</div>
);
};
export default MbtiColorInfo;
다음과 같이 쿼리 키를 전역 상태인 filterMbti로 특정 지으면서 바뀔때마다 새로운 데이터가 패칭되도록 해주었다. 또한 offset값을 10씩 더해주는 방식으로 진행하였음.
마지막으로 react-intersection-observer 라이브러리의 useInView 훅과 useEffect를 사용하여 뷰포트에 들어오고 다음 페이지가 있는 경우 10개씩 더 랜더링 해주도록 구현하였다.