버킷리스트 커뮤니티 사이트 글 목록 페이지를 구현하던 중 무한스크롤 기능을 구현하기로 했고, https://slog.website/post/8 블로그의 글을 참고하여 매우 쉽게 구현했다.
npm install react-intersection-observer
import React from "react"
import { useInView } from "react-intersection-observer"
const App = () => {
const [ref, inView] = useInView()
return (
<div ref={ref}>
Element {inView.toString()}
</div>
)
}
export default App
div에 ref를 걸어주고, 사용자가 div 요소를 보면 inView가 true, 안보면 false로 자동으로 변경된다.
이 방법을 이용하여 무한스크롤 페이지를 구현했다.
import { useInView } from "react-intersection-observer";
//생략
function PostList() {
const [items, setItems] = useState([]);
const [page, setPage] = useState(0);
const [loading, setLoading] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [ref, inView] = useInView();
const getItems = useCallback(async () => { //서버에 데이터 페이지별로 요청
setLoading(true);
await axios
.get(`url/page={page}`)
.then((res) => {
...
})
.catch(() => {
...
});
setLoading(false);
}, [page]);
useEffect(() => { // getItems가 바뀔때 마다 데이터 불러오기
getItems();
}, [getItems]);
useEffect(() => {
// 사용자가 마지막 요소를 보고 있고, 로딩 중이 아니라면 page+=1
if (inView && !loading) {
setIsLoading(true);
setTimeout(() => {
setPage((prevState) => prevState + 1);
setIsLoading(0);
}, 1500);
}
}, [inView]);
return (
<div style={{ background: "var(--color-skin)", width: "100%", padding: "50px" }}>
<div className="postList">
{items.map((item, i) => {
if (item) {
return items.length - 1 == i ?
<Post ref={ref} /> : <Post /> // 마지막 요소에 ref 걸기
//생략
);
getItems 함수는 page가 바뀔 때 마다 재생성 되는 함수이다. 이 함수를 useEffect에 넣게되면 결론적으로 page가 바뀌면 서버에 데이터를 요청하게 된다. 즉 page값만 증가시키게 되면 바로 다음 page에 대한 데이터를 서버에서 가져온다.
두 번째 useEffect는 inView가 true, 즉 사용자가 마지막 요소를 보고 있고 로딩중(서버에서 데이터를 받는 중)이 아니라면 page를 증가시킨다. isLoading state는 스크롤이 끝에 가게되면 1.5초간 로딩 애니메이션을 보여주기 위해 정의했다.