이번 포스팅 때는 React query를 이용해 Infinite scroll, 즉 무한스크롤을 구현하는 방법에 대해 설명하고자 한다. 무한스크롤을 구현하기 위해서는 React query를 이용할지라도, IntersectionObserver
api를 이용해 현재 스크롤이 마지막 요소까지 도달했는지를 관찰하는 작업이 필요하다.
따라서 본 포스팅은 React query의 useInfiniteQuery
api와 IntersectionObserver
로직을 이용해 무한스크롤을 구현하는 방법에 대해 서술하는 데 목적이 있다.
무한스크롤을 구현하기 위해서는 아래 차례를 따라가면 된다.
useInfiniteQuery
api를 이용해 무한스크롤 api를 구현한다.IntersectionObserver
로직을 구현한다.isIntersecting
될 때 다음 page data를 fetch할 수 있게끔 한다.콜백 ref
를 이용해 IntersectionObserver의 observer target을 로딩바로 설정한다.각 차례를 코드를 통해 차근차근 설명하도록 하겠다.
const infiniteServiceWorker = axios.create({
baseURL: "https://jsonplaceholder.typicode.com"
});
export function useDummyJsonQuery() {
return useInfiniteQuery(
["todos"],
async ({ pageParam = 1 }) => {
const { data } = await infiniteServiceWorker.get(
`/todos?_start=${pageParam}&_limit=10`
);
if (data.length < 10) return { result: data, nextPage: undefined };
return {
result: data,
nextPage: pageParam + 1
};
},
{
getNextPageParam: (lastPage, pages) => lastPage.nextPage ?? undefined
}
);
}
const {
data: todoPayload,
isFetchingNextPage,
fetchNextPage,
hasNextPage
} = useDummyJsonQuery();
const todoList = changeInfiniteScrollDataToArray(todoPayload);
...
return (
<div>
<div
style={{
display: "grid",
gap: "10px"
}}
>
{todoList.map((todo, index) => (
<Card key={uid(index)} todo={todo} />
))}
</div>
{hasNextPage && <div ref={(elem) => setTarget(elem)}>로딩중...</div>}
</div>
);
interface UseIntersectionObserverProps extends IntersectionObserverInit {
onIntersect: IntersectionObserverCallback;
options?: {
root?: Document;
rootMargin?: string;
threshold?: number;
};
}
const useIntersectionObserver = ({
onIntersect,
options = { root: null, rootMargin: "0px", threshold: 0 }
}: UseIntersectionObserverProps) => {
const [target, setTarget] = React.useState<HTMLElement>(null);
React.useEffect(() => {
if (!target) return;
const observer = new IntersectionObserver(onIntersect, options);
observer.observe(target);
return () => target && observer.disconnect();
}, [target]);
return { setTarget };
};
IntersectionObserver 로직은 무한스크롤 뿐만 아니라 Lazy loading 등 여러 곳에서 사용되므로 재사용성을 위해 custom hook으로 분리하였다.
콜백 ref
를 이용해 로딩바 dom으로 target을 설정const onIntersect = React.useCallback(
(entries: IntersectionObserverEntry[]) => {
const [target] = entries;
if (target.isIntersecting && hasNextPage) {
fetchNextPage();
}
},
[hasNextPage, fetchNextPage, isFetchingNextPage]
);
const { setTarget } = useIntersectionObserver({
onIntersect,
options: {
rootMargin: "10%",
threshold: 0.25
}
});
const { setTarget } = useIntersectionObserver({
onIntersect,
options: {
rootMargin: "10%",
threshold: 0.25
}
});
return (
<div>
<div
style={{
display: "grid",
gap: "10px"
}}
>
{todoList.map((todo, index) => (
<Card key={uid(index)} todo={todo} />
))}
</div>
{hasNextPage && <div ref={(elem) => setTarget(elem)}>로딩중...</div>}
</div>
);
전체 코드는 다음과 같으며 위의 설명을 참고하면서 이해해주길 바란다.
IntersectionObserver 로직을 hook으로 분리하면서 콜백 ref를 처음 알게되었다. 아직 리액트를 잘 모른다는 생각이 들었다.
Infinite scoll을 구현하시는 분들에게 이 글이 조금이라도 도움이 되길 바란다.
(ref)