Education/Wanted Pre-Onboarding FE Course

[TIL] 22.05.12 프리온보딩 Day 8 - Infinite scroll(intersection-observer)

SH_Roh 2022. 5. 13. 23:06
반응형

드디어.. 무한 스크롤을 완성했다. 사실 깔끔하게 짜진 못한 것 같은데 그래도 기능은 잘 동작하는 중이다!

무한 스크롤 구현에는 intersection-observer을 이용했다.

 

 

영화 정보는 한 번에 10개씩 받아오고 맨 아래로 내리면 추가적으로 10개씩 받아온다.

const [movieList, setMovieList, resetMovieList] = useRecoil(movieListState) // 영화 api로 받아온 영화 정보
const [currPage, setCurrPage] = useState<number>(1) // 현재 페이지 번호
const [isLoading, setIsLoading] = useState<boolean>(false) // 로딩 중 여부
const [totalResults, setTotalResults] = useState<number>(0) // 영화 총 갯수

const pageEnd = useRef(null)

먼저 state들을 정의해주고 useRef를 이용해서 마지막 요소를 observe할 수 있도록 한다.

 

로딩 상태일 때만 Loading이 보일 수 있게 하고 해당 요소에 ref를 붙여주면 된다.

return (
    <div className={styles.search}>
      <main className={styles.searchMain}>
        {movieList.length ? (
          <ul className={styles.itemList}>
            {movieList.map((movie, idx) => {
              const key = `movie-${idx}`
              return <Item key={key} movie={movie} />
            })}
            <li className={styles.observeLi} ref={pageEnd}>
              {isLoading && 'Loading... 😊'}
            </li>
          </ul>
        ) : (
          <div>에러 메시지</div>
        )}
      </main>
    </div>
  )

 

useState안에서 intersection observer를 사용해주면 된다.

target의 isIntersecting이 true라면 (스크롤이 맨 아래 요소로 도달했으면) loadMore() 함수를 실행한다. 

useEffect(() => {
  let observer: IntersectionObserver

  if (pageEnd.current) {
    observer = new IntersectionObserver(
      async (entries) => {
        const target = entries[0]

        if (target.isIntersecting) {
          if (currPage * 10 < totalResults) { // 영화의 총 갯수보다 많이 호출되지 않도록 함.
            loadMore()
          }
        }
      },
      { threshold: 1.0 }
    )

    observer.observe(pageEnd.current) 
  }
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, [totalResults])

 

loadMore()에서는 현재 페이지를 1씩 증가시켜주고, 현재 페이지가 변화할 때마다 loadMovies를 실행해 새로운 영화 데이터를 불러와준다.

// 현재 페이지가 변하면 다음 페이지의 영화를 불러옴.
useEffect(() => {
  loadMovies(currPage)
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, [currPage])

// 현재 페이지 +1
const loadMore = () => {
  setCurrPage((prev) => prev + 1)
}

// 영화 정보 불러옴.
const loadMovies = async (page: number) => {
  try {
    const { data } = await getMovieListApi({
      s: searchInputVal,
      page,
    })
     if (page === 1) {
      setTotalResults(data.totalResults)
    }
     if (data.Response === 'True') {
      setMovieList((prev) => [...prev, ...data.Search])
    }
  } finally {
    setIsSubmitted(false)
    setIsLoading(true)
  }
}

 

 

👀 전체 코드


어찌저찌 구현은 완료했지만 useEffect에서 함수 이름도 같이 넣어주면 요청이 계속 가는 경우가 생겨 어쩔 수 없이 그 줄에서만 eslint를 무시하는 옵션을 넣어주었다.

 

이틀동안 뭔가 될 것같은데 자꾸 오류가 나서 스트레스를 많이 받았는데 그래도 해결해서 다행이다 🥲

반응형