Skip to content

useIntersectionObserver 사용법 (무한스크롤 쉽게 구현하기)

Kim Dong Hyun edited this page Nov 7, 2023 · 2 revisions

현재 물건 목록 페이지 구현하면서, useIntersectionObserver를 hooks폴더에 생성하였습니다.

내부적으로 다음과 같이 구현되어있으며,

import { RefObject, useEffect, useState } from 'react'

export function useIntersectionObserver(
  ref: RefObject<HTMLElement>,
  { threshold = 0, root = null, rootMargin = '0%' }: IntersectionObserverInit,
) {
  const [entry, setEntry] = useState<IntersectionObserverEntry>()

  useEffect(() => {
    if (!ref.current) {
      return
    }

    const observer = new IntersectionObserver(
      ([entry]) => {
        setEntry(entry)
      },
      { threshold, root, rootMargin },
    )

    observer.observe(ref.current)

    return () => observer.disconnect()
  }, [ref, threshold, root, rootMargin])

  return entry
}

사용하실 때는 아래 코드를 참고하시면 됩니다.

'use client'

import { useEffect, useRef } from 'react'
import React from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import Image from 'next/image'
import TradeStateCard from '@/components/domain/card/trade-state-card'
import Assets from '@/config/assets'
import { useItemsQuery } from '@/hooks/api/useItemsQuery'
import { useIntersectionObserver } from '@/hooks/useIntersectionObserver'
import { Item } from '@/types'
import FilterFormDialog from '../filter-form-dialog'
import SearchInput from '../search-input'

type ItemFilterInputs = {
  category: string
  priceRange: string
  cardTitle: string
}

const ItemList = () => {
  const methods = useForm<ItemFilterInputs>()
  const { getValues } = methods

  // 1. ref 요소 선언
  const lastElementRef = useRef<HTMLDivElement | null>(null)
  // 2. 훅 정의 및 threshold 옵션 설정하기
  const entry = useIntersectionObserver(lastElementRef, { threshold: 1.0 }) 

  const { data, fetchNextPage, isFetchingNextPage } = useItemsQuery({
    category: getValues('category'),
    priceRange: getValues('priceRange'),
    cardTitle: getValues('cardTitle'),
    status: ['TRADE_AVAILABLE'],
    size: 5,
  })
  // 3. useEffect 설정하기, entry.isIntersecting의 값이 바뀔때마다 다음 페이지 데이터 조회
  useEffect(() => {
    if (isFetchingNextPage) {
      return
    }

    if (entry?.isIntersecting) {
      fetchNextPage()
    }
  }, [entry?.isIntersecting, fetchNextPage, isFetchingNextPage])

  return (
    <div>
      <div className="h-9 flex justify-between items-center mb-6">
        <FormProvider {...methods}>
          <SearchInput />

          <div className="h-6 flex gap-2">
            <Image src={Assets.filterIcon} alt="필터 아이콘" />{' '}
            <FilterFormDialog />
          </div>
        </FormProvider>
      </div>
      <div>
        {data?.pages.map((group, i) => (
          <React.Fragment key={i}>
            {group.map((item: Item) => (
              <TradeStateCard key={item._id} item={item} className="mb-6" />
            ))}
          </React.Fragment>
        ))}
        {isFetchingNextPage && '데이터 불러오는 중'}
      </div>
      {/* 1. ref 요소 선언*/}
      <div ref={lastElementRef} />
    </div>
  )
}
export default ItemList