From 0d1e3ffbc07d535be2dc65370e7be93f8044c029 Mon Sep 17 00:00:00 2001 From: polee <43488305+poiu694@users.noreply.github.com> Date: Sun, 22 Sep 2024 20:42:58 +0900 Subject: [PATCH] feat: infinite scroll just working on frontend bottomsheet (#136) * feat: infinite scroll just working on frontend in bottomsheet * chore: delete log --- .../map/[mapId]/place-list-bottom-sheet.tsx | 21 ++++-- src/components/place/place-list-item.tsx | 2 +- src/hooks/use-infinite-scroll.ts | 70 +++++++++++++++++++ 3 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 src/hooks/use-infinite-scroll.ts diff --git a/src/app/map/[mapId]/place-list-bottom-sheet.tsx b/src/app/map/[mapId]/place-list-bottom-sheet.tsx index 726e7397..ae7e6977 100644 --- a/src/app/map/[mapId]/place-list-bottom-sheet.tsx +++ b/src/app/map/[mapId]/place-list-bottom-sheet.tsx @@ -1,6 +1,6 @@ 'use client' -import { forwardRef, Fragment, useEffect, useState } from 'react' +import { forwardRef, useEffect, useState } from 'react' import type { FilterIdsType } from './page' @@ -11,6 +11,7 @@ import PlaceListItem from '@/components/place/place-list-item' import useFetch from '@/hooks/use-fetch' import { APIError } from '@/models/api/index' import type { PlaceType } from '@/models/api/place' +import { useInfiniteScroll } from '@/hooks/use-infinite-scroll' import { api } from '@/utils/api' interface PlaceListBottomSheetProps { @@ -38,6 +39,11 @@ const PlaceListBottomSheet = forwardRef< (selectedFilter?.category !== 'all' ? 1 : 0) + (selectedFilter?.tags.length ?? 0) + const { data: slicedPlaceList, listRef } = useInfiniteScroll({ + totalData: placeList, + itemsPerPage: 10, + }) + const getIsLike = (place: PlaceType): boolean => { if (typeof userId === 'undefined') return false @@ -119,8 +125,11 @@ const PlaceListBottomSheet = forwardRef< 필터 - {placeList.length > 0 ? ( -
+ {slicedPlaceList.length > 0 ? ( +
    { if (typeof ref !== 'function' && ref?.current) { @@ -128,8 +137,8 @@ const PlaceListBottomSheet = forwardRef< } }} > - {placeList.map((place) => ( - + {slicedPlaceList.map((place, index) => ( +

  • - +
  • ))}
diff --git a/src/components/place/place-list-item.tsx b/src/components/place/place-list-item.tsx index ddb4a2b2..58d7875f 100644 --- a/src/components/place/place-list-item.tsx +++ b/src/components/place/place-list-item.tsx @@ -50,7 +50,7 @@ const PlaceListItem = ({ ))} diff --git a/src/hooks/use-infinite-scroll.ts b/src/hooks/use-infinite-scroll.ts new file mode 100644 index 00000000..56e32f54 --- /dev/null +++ b/src/hooks/use-infinite-scroll.ts @@ -0,0 +1,70 @@ +import debounce from 'lodash.debounce' +import { useState, useEffect, useCallback, useRef } from 'react' + +interface UseInfiniteScrollProps { + totalData: T[] + itemsPerPage: number + threshold?: number +} + +const INITIAL_PAGE = 1 + +export const useInfiniteScroll = ({ + totalData, + itemsPerPage, + threshold = 100, +}: UseInfiniteScrollProps) => { + const [data, setData] = useState(totalData.slice(0, itemsPerPage)) + const [page, setPage] = useState(INITIAL_PAGE) + const [isLoading, setIsLoading] = useState(false) + const [hasMore, setHasMore] = useState(totalData.length > itemsPerPage) + const listRef = useRef(null) + + const loadMoreData = useCallback(() => { + if (isLoading || !hasMore) return + + setIsLoading(true) + setTimeout(() => { + const startIndex = page * itemsPerPage + const endIndex = startIndex + itemsPerPage + const newData = totalData.slice(startIndex, endIndex) + + if (newData.length === 0) { + setHasMore(false) + } else { + setData((prevData) => [...prevData, ...newData]) + setPage((prevPage) => prevPage + 1) + } + + setIsLoading(false) + }, 100) + }, [page, itemsPerPage, totalData, isLoading, hasMore]) + + const handleScroll = useCallback(() => { + if (!listRef.current) return + + const { scrollTop, scrollHeight, clientHeight } = listRef.current + if ( + scrollHeight - scrollTop <= clientHeight + threshold && + !isLoading && + hasMore + ) { + loadMoreData() + } + }, [isLoading, hasMore, loadMoreData, threshold]) + + useEffect(() => { + const ref = listRef.current + if (!ref) return + + const debouncedHandleScroll = debounce(handleScroll, 300) + + ref.addEventListener('scroll', debouncedHandleScroll) + + return () => { + ref.removeEventListener('scroll', debouncedHandleScroll) + } + }, [handleScroll]) + + return { data, isLoading, listRef } +}