diff --git a/src/drawer/apis/getBookMarked.ts b/src/drawer/apis/getBookMarked.ts index 78803625..b7d4564c 100644 --- a/src/drawer/apis/getBookMarked.ts +++ b/src/drawer/apis/getBookMarked.ts @@ -1,9 +1,9 @@ import { soomsilClient } from '@/apis'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; import { ProductResponses } from '../types/product.type'; -export const getBookmarked = async ({ responseType, page }: RankingRequestParams) => { +export const getBookmarked = async ({ responseType, page }: ProductRequestParams) => { const response = await soomsilClient.get('/v2/drawer/my-bookmarked', { params: { responseType, diff --git a/src/drawer/apis/getMyProduct.ts b/src/drawer/apis/getMyProduct.ts index c83566c9..19a1093f 100644 --- a/src/drawer/apis/getMyProduct.ts +++ b/src/drawer/apis/getMyProduct.ts @@ -1,9 +1,9 @@ import { soomsilClient } from '@/apis'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; import { ProductResponses } from '../types/product.type'; -export const getMyProduct = async ({ responseType, page }: RankingRequestParams) => { +export const getMyProduct = async ({ responseType, page }: ProductRequestParams) => { const response = await soomsilClient.get('/v2/drawer/my-registered', { params: { responseType, diff --git a/src/drawer/apis/getNewRelease.ts b/src/drawer/apis/getNewRelease.ts index b89e9379..6efea4af 100644 --- a/src/drawer/apis/getNewRelease.ts +++ b/src/drawer/apis/getNewRelease.ts @@ -1,9 +1,9 @@ import { soomsilClient } from '@/apis'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; import { ProductResponses } from '../types/product.type'; -export const getNewRelease = async ({ responseType, category, page }: RankingRequestParams) => { +export const getNewRelease = async ({ responseType, category, page }: ProductRequestParams) => { const response = await soomsilClient.get('/v2/drawer/new-release', { params: { responseType, diff --git a/src/drawer/apis/getRanking.ts b/src/drawer/apis/getRanking.ts index 2f9a3430..e9e11c08 100644 --- a/src/drawer/apis/getRanking.ts +++ b/src/drawer/apis/getRanking.ts @@ -1,9 +1,9 @@ import { soomsilClient } from '@/apis'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; import { ProductResponses } from '../types/product.type'; -export const getRanking = async ({ responseType, category, page }: RankingRequestParams) => { +export const getRanking = async ({ responseType, category, page }: ProductRequestParams) => { const response = await soomsilClient.get('/v2/drawer/rank', { params: { responseType, diff --git a/src/drawer/constants/page.constant.ts b/src/drawer/constants/page.constant.ts new file mode 100644 index 00000000..720b59be --- /dev/null +++ b/src/drawer/constants/page.constant.ts @@ -0,0 +1 @@ +export const PRODUCTS_PER_PAGE = 21; diff --git a/src/drawer/hooks/useGetBookMarked.ts b/src/drawer/hooks/useGetBookMarked.ts index 3f3e3122..8e512286 100644 --- a/src/drawer/hooks/useGetBookMarked.ts +++ b/src/drawer/hooks/useGetBookMarked.ts @@ -1,15 +1,29 @@ -import { useSuspenseQuery } from '@tanstack/react-query'; +import { InfiniteData, useSuspenseInfiniteQuery } from '@tanstack/react-query'; import { getBookmarked } from '../apis/getBookMarked'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { PRODUCTS_PER_PAGE } from '../constants/page.constant'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; +import { ProductResponses, ProductResult } from '../types/product.type'; -export const useGetBookmarked = ({ responseType, page }: RankingRequestParams) => { - return useSuspenseQuery({ - queryKey: ['bookmarked', { responseType, page }], - queryFn: () => { - return getBookmarked({ responseType, page }); +type ProductRequestParamsWithoutPage = Omit; + +export const useGetBookmarked = ({ responseType }: ProductRequestParamsWithoutPage) => { + return useSuspenseInfiniteQuery< + ProductResponses[], + Error, + InfiniteData, + string[], + number + >({ + queryKey: ['bookmarked', responseType], + queryFn: ({ pageParam }) => + getBookmarked({ responseType, page: pageParam }).then((data) => data.productList) as Promise< + ProductResponses[] + >, + initialPageParam: 0, + getNextPageParam: (lastPage, allPages) => { + return lastPage.length === PRODUCTS_PER_PAGE ? allPages.length : undefined; }, retry: false, - select: (data) => data.productList, }); }; diff --git a/src/drawer/hooks/useGetMyRegistered.ts b/src/drawer/hooks/useGetMyRegistered.ts index de0e35b1..acfec154 100644 --- a/src/drawer/hooks/useGetMyRegistered.ts +++ b/src/drawer/hooks/useGetMyRegistered.ts @@ -1,15 +1,29 @@ -import { useSuspenseQuery } from '@tanstack/react-query'; +import { InfiniteData, useSuspenseInfiniteQuery } from '@tanstack/react-query'; import { getMyProduct } from '../apis/getMyProduct'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { PRODUCTS_PER_PAGE } from '../constants/page.constant'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; +import { ProductResponses, ProductResult } from '../types/product.type'; -export const useGetMyRegistered = ({ responseType, page }: RankingRequestParams) => { - return useSuspenseQuery({ - queryKey: ['myRegistered', { responseType, page }], - queryFn: () => { - return getMyProduct({ responseType, page }); +type ProductRequestParamsWithoutPage = Omit; + +export const useGetMyRegistered = ({ responseType }: ProductRequestParamsWithoutPage) => { + return useSuspenseInfiniteQuery< + ProductResponses[], + Error, + InfiniteData, + string[], + number + >({ + queryKey: ['myRegistered', responseType], + queryFn: ({ pageParam }) => + getMyProduct({ responseType, page: pageParam }).then((data) => data.productList) as Promise< + ProductResponses[] + >, + initialPageParam: 0, + getNextPageParam: (lastPage, allPages) => { + return lastPage.length === PRODUCTS_PER_PAGE ? allPages.length : undefined; }, retry: false, - select: (data) => data.productList, }); }; diff --git a/src/drawer/hooks/useGetNewRelease.ts b/src/drawer/hooks/useGetNewRelease.ts index 6301c487..9519e149 100644 --- a/src/drawer/hooks/useGetNewRelease.ts +++ b/src/drawer/hooks/useGetNewRelease.ts @@ -1,19 +1,31 @@ -import { useQuery } from '@tanstack/react-query'; +import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query'; import { getNewRelease } from '@/drawer/apis/getNewRelease'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { PRODUCTS_PER_PAGE } from '../constants/page.constant'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; +import { ProductResponses, ProductResult } from '../types/product.type'; -export const useGetNewRelease = ({ responseType, category, page }: RankingRequestParams) => { - return useQuery({ - queryKey: ['newReleases', { responseType, category, page }], - queryFn: () => { - return getNewRelease({ +type ProductRequestParamsWithoutPage = Omit; + +export const useGetNewRelease = ({ responseType, category }: ProductRequestParamsWithoutPage) => { + return useInfiniteQuery< + ProductResponses[], + Error, + InfiniteData, + string[], + number + >({ + queryKey: ['newReleases', responseType, category ?? ''], + queryFn: ({ pageParam }) => + getNewRelease({ responseType, category, - page, - }); + page: pageParam, + }).then((data) => data.productList) as Promise, + initialPageParam: 0, + getNextPageParam: (lastPage, allPages) => { + return lastPage.length === PRODUCTS_PER_PAGE ? allPages.length : undefined; }, - select: (data) => data.productList, }); }; diff --git a/src/drawer/hooks/useGetProductByProvider.ts b/src/drawer/hooks/useGetProductByProvider.ts index 46f75a36..738d08b1 100644 --- a/src/drawer/hooks/useGetProductByProvider.ts +++ b/src/drawer/hooks/useGetProductByProvider.ts @@ -4,10 +4,9 @@ import { useRecoilValue } from 'recoil'; import { CategoryState } from '@/drawer/recoil/CategoryState'; import { getProductByProvider } from '../apis/getProductByProvider'; +import { PRODUCTS_PER_PAGE } from '../constants/page.constant'; import { ProviderProductResponses } from '../types/product.type'; -const PRODUCTS_PER_PAGE = 21; - export const useGetProductByProvider = ({ providerId }: { providerId: string }) => { const selectedCategory = useRecoilValue(CategoryState); diff --git a/src/drawer/hooks/useGetStarRank.ts b/src/drawer/hooks/useGetStarRank.ts index 9248b936..71fb6d50 100644 --- a/src/drawer/hooks/useGetStarRank.ts +++ b/src/drawer/hooks/useGetStarRank.ts @@ -1,18 +1,30 @@ -import { useQuery } from '@tanstack/react-query'; +import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query'; import { getRanking } from '../apis/getRanking'; -import { RankingRequestParams } from '../types/RankingRequestParams.type'; +import { PRODUCTS_PER_PAGE } from '../constants/page.constant'; +import { ProductRequestParams } from '../types/ProductRequestParams.type'; +import { ProductResponses, ProductResult } from '../types/product.type'; -export const useGetStarRank = ({ responseType, category, page }: RankingRequestParams) => { - return useQuery({ - queryKey: ['getRanking', { responseType, category, page }], - queryFn: () => { - return getRanking({ +type ProductRequestParamsWithoutPage = Omit; + +export const useGetStarRank = ({ responseType, category }: ProductRequestParamsWithoutPage) => { + return useInfiniteQuery< + ProductResponses[], + Error, + InfiniteData, + string[], + number + >({ + queryKey: ['getRanking', responseType, category ?? ''], + queryFn: ({ pageParam }) => + getRanking({ responseType, category, - page, - }); + page: pageParam, + }).then((data) => data.productList) as Promise, + initialPageParam: 0, + getNextPageParam: (lastPage, allPages) => { + return lastPage.length === PRODUCTS_PER_PAGE ? allPages.length : undefined; }, - select: (data) => data.productList, }); }; diff --git a/src/drawer/pages/MyDrawer/MyDrawer.tsx b/src/drawer/pages/MyDrawer/MyDrawer.tsx index 40cdf930..06e68032 100644 --- a/src/drawer/pages/MyDrawer/MyDrawer.tsx +++ b/src/drawer/pages/MyDrawer/MyDrawer.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useCallback, useRef } from 'react'; import { useSearchParams } from 'react-router-dom'; @@ -16,8 +16,21 @@ export type TabType = 'STAR' | 'MYDRAWER'; export const MyDrawer = () => { const [searchParams, setSearchParams] = useSearchParams(); - const { data: bookmarkedData } = useGetBookmarked({ responseType: 'WEB', page: 0 }); - const { data: myProductData } = useGetMyRegistered({ responseType: 'WEB', page: 0 }); + + const { + data: bookmarkedData, + fetchNextPage: fetchNextBookmarkedPage, + hasNextPage: hasNextBookmarkedPage, + isLoading: isLoadingBookmarked, + } = useGetBookmarked({ responseType: 'WEB' }); + + const { + data: myProductData, + fetchNextPage: fetchNextMyProductPage, + hasNextPage: hasNextMyProductPage, + isLoading: isLoadingMyProduct, + } = useGetMyRegistered({ responseType: 'WEB' }); + const initialTab = searchParams.get('tab'); const [currentTab, setCurrentTab] = useState( @@ -25,6 +38,26 @@ export const MyDrawer = () => { ); const drawerData = currentTab === 'STAR' ? bookmarkedData : myProductData; + const fetchNextPage = currentTab === 'STAR' ? fetchNextBookmarkedPage : fetchNextMyProductPage; + const hasNextPage = currentTab === 'STAR' ? hasNextBookmarkedPage : hasNextMyProductPage; + const isLoading = currentTab === 'STAR' ? isLoadingBookmarked : isLoadingMyProduct; + + const observer = useRef(); + + const lastElementRef = useCallback( + (node: HTMLDivElement) => { + if (isLoading) return; + + if (observer.current) observer.current.disconnect(); + observer.current = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting && hasNextPage) { + fetchNextPage(); + } + }); + if (node) observer.current.observe(node); + }, + [isLoading, hasNextPage, fetchNextPage] + ); function isTabType(tab: string | null): tab is TabType { return tab === 'STAR' || tab === 'MYDRAWER'; @@ -55,13 +88,14 @@ export const MyDrawer = () => { /> - {drawerData.length > 0 ? ( + {drawerData.pages[0].length === 0 ? ( + + ) : ( <> {TAB_DESCRIPTION[currentTab]} - + page)} type={currentTab} /> +
- ) : ( - )} ); diff --git a/src/drawer/pages/NewRelease/NewRelease.tsx b/src/drawer/pages/NewRelease/NewRelease.tsx index 45c2bbb7..f81c5be2 100644 --- a/src/drawer/pages/NewRelease/NewRelease.tsx +++ b/src/drawer/pages/NewRelease/NewRelease.tsx @@ -1,3 +1,5 @@ +import { useCallback, useRef } from 'react'; + import { useRecoilValue } from 'recoil'; import { CategoryDropdownMenu } from '@/drawer/components/Category/CategoryDropdownMenu/CategoryDropdownMenu'; @@ -20,11 +22,33 @@ import { export const NewRelease = () => { const selectedCategory = useRecoilValue(CategoryState); - const { data: newReleases } = useGetNewRelease({ + const { + data: newReleases, + isLoading, + fetchNextPage, + hasNextPage, + } = useGetNewRelease({ responseType: 'WEB', category: selectedCategory, - page: 0, }); + + const observer = useRef(); + + const lastElementRef = useCallback( + (node: HTMLDivElement) => { + if (isLoading) return; + + if (observer.current) observer.current.disconnect(); + observer.current = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting && hasNextPage) { + fetchNextPage(); + } + }); + if (node) observer.current.observe(node); + }, + [isLoading, hasNextPage] + ); + const isSmallDesktop = useMediaQuery(SMALL_DESKTOP_MEDIA_QUERY); return ( @@ -41,18 +65,23 @@ export const NewRelease = () => { {newReleases && - newReleases.map((product) => ( - - ))} + newReleases.pages.flat().map((product, productIndex, flatArray) => { + const isLastProduct = productIndex === flatArray.length - 1; + return ( +
+ + {isLastProduct &&
} +
+ ); + })}
diff --git a/src/drawer/pages/Ranking/Ranking.tsx b/src/drawer/pages/Ranking/Ranking.tsx index b173bb09..1c1b61b4 100644 --- a/src/drawer/pages/Ranking/Ranking.tsx +++ b/src/drawer/pages/Ranking/Ranking.tsx @@ -30,12 +30,10 @@ export const Ranking = () => { const { data: newReleases } = useGetNewRelease({ responseType: 'WEB', category: selectedCategory, - page: 0, }); const { data: rankings } = useGetStarRank({ responseType: 'WEB', category: selectedCategory, - page: 0, }); const isSmallDesktop = useMediaQuery(SMALL_DESKTOP_MEDIA_QUERY); @@ -63,7 +61,7 @@ export const Ranking = () => { {rankings && - rankings + rankings.pages[0] .slice(0, 3) .map((product) => ( { {newReleases && - newReleases + newReleases.pages[0] .slice(0, 3) .map((product) => ( { const selectedCategory = useRecoilValue(CategoryState); - const { data: rankings } = useGetStarRank({ + + const { + data: rankings, + isLoading, + fetchNextPage, + hasNextPage, + } = useGetStarRank({ responseType: 'WEB', category: selectedCategory, - page: 0, }); + + const observer = useRef(); + + const lastElementRef = useCallback( + (node: HTMLDivElement) => { + if (isLoading) return; + + if (observer.current) observer.current.disconnect(); + observer.current = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting && hasNextPage) { + fetchNextPage(); + } + }); + if (node) observer.current.observe(node); + }, + [isLoading, hasNextPage] + ); + const isSmallDesktop = useMediaQuery(SMALL_DESKTOP_MEDIA_QUERY); return ( @@ -41,18 +66,23 @@ export const StarRanking = () => { {rankings && - rankings.map((product) => ( - - ))} + rankings.pages.flat().map((product, productIndex, flatArray) => { + const isLastProduct = productIndex === flatArray.length - 1; + return ( +
+ + {isLastProduct &&
} +
+ ); + })}
diff --git a/src/drawer/types/RankingRequestParams.type.ts b/src/drawer/types/ProductRequestParams.type.ts similarity index 61% rename from src/drawer/types/RankingRequestParams.type.ts rename to src/drawer/types/ProductRequestParams.type.ts index f4eeee84..8456eb67 100644 --- a/src/drawer/types/RankingRequestParams.type.ts +++ b/src/drawer/types/ProductRequestParams.type.ts @@ -1,4 +1,4 @@ -export interface RankingRequestParams { +export interface ProductRequestParams { responseType: string; category?: string; page: number;