개발하고 있는 각 강의 영상에 유저들이 미션을 수행한 것을 볼 수 있는 미션광장
이 있는데, 이 공간을 무한스크롤로 구현하려고 한다.
무한스크롤 (Infinite Scroll) : 게시판 글 리스트처럼 많은 데이터를 배열로 받아오는 경우, 그 데이터가 너무 방대해지면 api 요청으로 데이터를 받아오는 시간이 오래걸릴 수 밖에 없다. 이 문제를 해결하기 위해 일반적으로 페이지네이션(Pagination)을 사용하여 데이터를 일정 개수씩 분리해 받아오는 방식으로 해결했다. 페이지네이션 또한 좋은 선택지이지만, 오늘 날 모바일 기기를 이용해 웹에 접속하는 경우가 매우 많다보니 터치횟수를 최소화하고 콘텐츠를 끊김없이 보여줄 수 있는 무한스크롤이 더 좋은 대안이 될 수 있다.
무한스크롤은 한 페이지의 스크롤의 바닥에 도달할 때 API를 호출해 데이터를 받아오는 방식이다.
2번의 방법을 쉬운 인터페이스로 구현된 라이브러리를 사용할 예정이다.
(프로젝트에서 이미 이 라이브러리를 설치하여 사용중이었다!)
관찰하는 객체(observer) 하나를 ref로 설정한 후 해당하는 객체가 화면에 보이면 특정 코드를 실행시킬 수 있다.
https://github.com/thebuilder/react-intersection-observer#readme
npm install react-intersection-observer
import { useInView } from 'react-intersection-observer';
<div>
가 보일 때 console.log를 호출하는 코드const AssignmentPlaza = () => {
const [ref, inView] = useInView();
useEffect(() => {
if (inView) console.log("get Data API Call,")
}, [inView]);
return (
<div>
<span>API 호출 영역</span>
</div>
);
}
바닥을 감지하는 것을 구현했으니, 이제 바닥을 감지했을 때 API를 call하면 된다. tanstack-query에서는 무한스크롤을 편하게 구현할 수 있도록 돕는 useInfiniteQuery라는 훅을 제공하고 있다.
https://tanstack.com/query/v4/docs/react/reference/useInfiniteQuery
하나의 미션광장은 각 VOD(curriculum_id)마다 데이터가 생성되므로 curriculum_id
를 query key로 사용
구현
const useGetAssignmentPlaza = ({ curriculum_id }) => {
// fetch API
const getAssignmentPlaza = async ({ pageParam = 1 }) => {
const response = await client.get(`${BASE_URL}/${curriculum_id}/${pageParam}`);
return {
current_page: pageParam,
isLast: response.data.results.total_page === response.data.results.page,
page_data: response.data.results,
};
};
// useInfiniteQuery Hook
const {
data: getData,
fetchNextPage: getNextPage,
isSuccess: getDataIsSuccess,
hasNextPage: getNextPageIsPossible,
} = useInfiniteQuery(['VOD_ASSIGNMENT', curriculum_id], getAssignmentPlaza, {
getNextPageParam: (lastPage, pages) => {
if (!lastPage.isLast) return lastPage.current_page + 1;
return undefined;
},
enabled: !!curriculum_id,
});
return { getData, getNextPage, getDataIsSuccess, getNextPageIsPossible };
};
const AssignmentPlaza = ({ curriculumData }) => {
const { getData, getNextPage, getDataIsSuccess, getNextPageIsPossible } = useGetAssignmentPlaza({
curriculum_id: curriculumData.id,
});
const [ref, inView] = useInView();
const [imgSrc, setImgSrc] = useState('');
useEffect(() => {
// 스크롤 맨 밑에 도달했을 때 데이터를 가져옴
if (inView && getNextPageIsPossible) {
getNextPage();
}
}, [inView, getData]);
return (
<div>
{getDataIsSuccess &&
getData?.pages.map((page, pageIdx) => {
const plazaPage = page.page_data.list;
const isComment = page.page_data.list.length > 0;
// 댓글이 없을 때
if (!isComment)
return (
<div className="assignment_plaza_no_comment_container" key={pageIdx}>
<Text text="등록된 미션이 없습니다." weight="400" size="15px" lineHeight="21px" color={color.grey100} />
</div>
);
return plazaPage.map((comment, idx) => {
const isLastComment = getData.pages.length - 1 === pageIdx && plazaPage.length - 1 === idx;
return (
<div key={comment.id} className="assignment_plaza_comment_container">
<div className="assignment_plaza_profile_container">
<img className="assignment_plaza_profile_img" src={comment.user_photo} />
<div className="assignment_plaza_profile_title">
<Text text={comment.user_name} weight="500" size="14px" lineHeight="19.6px" color={color.grey25} />
<Text
text={dayjs(comment.created_at).format('YYYY년 MM월 DD일 a h:mm')}
weight="400"
size="12px"
lineHeight="16.8px"
color={color.grey200}
/>
</div>
</div>
<div>
<Text
text={comment.original_content}
weight="400"
size="14px"
lineHeight="19.6px"
color={color.grey25}
marginBottom="12px"
/>
{comment.photo_list.length > 0 && (
<div className={`assignment_plaza_image_attachment_container size${comment.photo_list.length}`}>
{comment.photo_list.map((item, idx) => {
return (
<img
onClick={() => commentImageOnClick(item)}
className="assignment_plaza_image"
key={item}
src={item}
/>
);
})}
</div>
)}
</div>
{isLastComment && !page.isLast && <div ref={ref} className="loading_indicator" />}
</div>
);
});
})}
</div>
);
};
export default AssignmentPlaza;
참고 : [React] react-query useInfiniteQuery로 무한스크롤 구현하기
[React] react-Intersection-Observer 라이브러리를 이용해 무한스크롤 구현하기