페이지 하단에 도달하면 추가로 데이터를 페치하여 한 화면에 데이터를 추가로 로드하여 렌더링 하는 방식이다.
sns-app 프로젝트를 이와 같은 방식으로 구현하였다.

즉, 이미 스크롤 된 페이지 높이인 scrollTop과 사용자에게 보여지는 페이지 높이인 clientHeight가 페이지의 총 높이인 scrollHeight와 같거나 크면 페이지의 끝에 도달한 것이다.
scrollTop + clientHeight >= scrollHeight
이 방법을 이용하여 구현해 보았다.
우선 페이지에 처음 진입했을때 데이터를 페치해 준다.
react 사용시
useEffect(() => {
axios.post("http://localhost:7000/api/post/getPost").then((res) => {
if (res.data.success) {
dispatch({ type: ADD_POST, payload: res.data.doc });
} else {
console.log(res.data.err);
}
});
}, []);
next 사용시
export const getServerSideProps = wrapper.getServerSideProps(
(store) =>
async ({ req }) => {
try {
const res = await axios.post("http://localhost:7000/api/post/getPost");
const payload = await res.data.doc;
store.dispatch({ type: ADD_POST, payload });
} catch (err) {
console.log(err);
}
}
);
//이미 페치한 데이터는 스킵
const [skip, setSkip] = useState(8);
//로딩창 구현
const [isFetching, setIsFetching] = useState(false);
//추가 데이터를 페치하는 함수
const fetchMorePosts = async () => {
//로딩창 띄우기
await setIsFetching(true);
//axios로 추가 데이터 페치
await axios
.post("http://localhost:7000/api/post/getPost", { skip })
.then((res) => {
if (res.data.success) {
dispatch({ type: ADD_POST, payload: res.data.doc });
setSkip(skip + 8);
} else {
console.log(res.data.err);
}
});
//페칭이 완료되었으므로 로딩창 닫기
setIsFetching(false);
};
//scroll event handler 생성
const handleScroll = () => {
const scrollHeight = document.documentElement.scrollHeight;
const scrollTop = document.documentElement.scrollTop;
const clientHeight = document.documentElement.clientHeight;
if (scrollTop + clientHeight >= scrollHeight && isFetching === false) {
//페이지 끝에 도달했으므로 페칭
fetchMorePosts();
}
};
//scroll event listener 생성
useEffect(() => {
if (posts.length % 8 === 0) {
// handleScroll event listener 등록
window.addEventListener("scroll", handleScroll);
return () => {
// handleScroll event listener 해제
window.removeEventListener("scroll", handleScroll);
};
}
});
스크롤 이벤트를 감지하는 event listener를 등록하고, 스크롤 끝에 닿았을 때 추가 데이터를 페치하는 함수를 실행한다.
추가 데이터를 페치하는 함수를 실행하면 isFetching을 이용해 로딩창이 뜨도록 하였다.
데이터는 8로 나누어 떨어지도록 구현하였으므로 8로 나누어 떨어지지 않으면 추가할 데이터가 없는 걸로 간주하여 scroll event listener를 실행하지 않았다.
const post = (state = initialState, action) => {
switch (action.type) {
case ADD_POST:
return [...state, ...action.payload];
case DELETE_POST:
const deletedPosts = state.filter((post) => post._id !== action.payload);
return [...deletedPosts];
default:
return state;
}
};
리듀서에서는 추가 데이터가 밑에서 로딩되도록 하였다.