-
Notifications
You must be signed in to change notification settings - Fork 2
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