개인공부/React

Intersection Observer API 로 무한 스크롤 구현해보기 (with React)

somm12 2024. 2. 4. 15:27

문제 상황

무한 스크롤을 구현했는데 문제가 있었다.

처음 렌더링이 되자마자 page가 setState에 의해 +1 증가가 되어서, 뒷 페이지가 먼저 렌더링 되었다.

여러 블로그글을 찾아본 결과, isLoading라는 상태값을 추가해서 페이지가 로딩 중이면 page+1 가 되지 않도록! 코드를 아래처럼 수정했다. 

import React, { useState, useEffect, useRef, useCallback } from "react";

const MainPage = () => {
//...
  const [postList, setPostList] = useState([]);
  const [page, setPage] = useState(1);
//..
  const [isLoading, setIsLoading] = useState(true);// 1️⃣ 처음 부터 로딩 시작

  const obsRef = useRef(null); //observer Element
  const endRef = useRef(false); //모든 글 로드 확인

  useEffect(() => {// page가 바뀌거나, 정렬 기준이 바뀐다면 post 가져오기.
    getPostLoadMore();
  }, [page, sort]);

  useEffect(() => {
    const observer = new IntersectionObserver(obsHandler, { threshold: 1 }); //  obsRef가 100% 가 보이면 로드하기.

    if (obsRef.current) observer.observe(obsRef.current);

    return () => {
      observer.disconnect();
    };
  }, [isLoading]);// isLoading 상태 값이 바뀌면서, observer는 다시 관찰(obsHandler함수 호출)

  const obsHandler = (entries) => {
    const target = entries[0];
   
    if (!endRef.current && target.isIntersecting && !isLoading) {//4️⃣ 현재 페이지 로딩 중이라면, page 값을 증가하지 않는다.
      setPage((prev) => prev + 1); //페이지 값 증가
    }
  };


  const getPostLoadMore = useCallback(async () => {

    setIsLoading(true);//2️⃣ 현재 페이지를 로딩하고 있음에 true
    let body = {
      sort,
      searchTerm,
      page,
    };

    try {
      const { data } = await axios.post("/api/post/list", body);

      if (data.success) {
        if (page === 1) {
          setPostList([...data.postList]);
        } else setPostList((prev) => [...prev, ...data.postList]);
        setIsLoading(false);// 3️⃣ 현재 페이지 로딩이 끝났음에 false
        if (data.postList.length < 8) {
          endRef.current = true;
        }

      }
    } catch (e) {
      console.log(e);
    }
  }, [page, sort, searchTerm]);



  return (
    <div className={style.mainWrapper}>
     //...
 
      <div className={style.observer} ref={obsRef}></div>
    </div>
  );
};

export default MainPage;

=> 첫 페이지가 먼저 요청이 되긴하지만, 바로 page가 +1 되버리면서 page2도 요청 되버리는 것 같았다.

그래서 현재 페이지가 로딩 중이라면, true를 isLoading 상태값에 할당하고, 로딩이 끝나면 false를 isLoading 상태값에 할당한다.

그러면 스크롤을 내리면서 observer가 obsRef를 발견을 해도!! 현재 페이지가 아직 로딩 중이라면, page 상태값을 증가 시키지 않을 것이다.

** 주의할 것은 맨 처음에 로딩할 때, 아직 데이터가 없기 때문에(가져오는 중) 제일 하단에 있는 obsRef가 위쪽으로 올라와서 observer가 해당 ref를 관찰 해버릴 수 있다는 점이다.