From 68d1114a538787f2e7b6ddeaa9c7b150782ba749 Mon Sep 17 00:00:00 2001 From: kyuhho Date: Sun, 14 Jul 2024 18:17:46 +0900 Subject: [PATCH] feat: migrate saved open consult to infinite query (#350) --- .../SavedOpenConsultResults.tsx | 100 ++++++- src/pages/Buyer/BuyerSavedCounselor.tsx | 268 ++---------------- 2 files changed, 115 insertions(+), 253 deletions(-) diff --git a/src/components/Buyer/BuyerSavedCounselor.tsx/SavedOpenConsultResults.tsx b/src/components/Buyer/BuyerSavedCounselor.tsx/SavedOpenConsultResults.tsx index a506d482..55bf3f19 100644 --- a/src/components/Buyer/BuyerSavedCounselor.tsx/SavedOpenConsultResults.tsx +++ b/src/components/Buyer/BuyerSavedCounselor.tsx/SavedOpenConsultResults.tsx @@ -1,19 +1,102 @@ import { openConsultApiObject } from 'pages/Buyer/BuyerConsult'; -import React from 'react'; import styled from 'styled-components'; import SavedOpenConsultCard from './SavedOpenConsultCard'; +import { useInfiniteQuery } from '@tanstack/react-query'; +import { getPostScraps } from 'api/get'; +import { useMemo } from 'react'; +import useInfiniteObserver from 'hooks/useInfiniteObserver'; +import { Space } from 'components/Common/Space'; +import { LoadingSpinner } from 'utils/LoadingSpinner'; +import EmptySection from 'components/Common/EmptySection'; + +// +// +// + +const INFINITE_POST_SCRAPS_QUERY_KEY = 'infiniteGetPostScraps'; + +const POST_SCRAPS_PER_PAGE = 6; + +// +// +// + +function SavedOpenConsultResults() { + // + // + // + const { + data: openConsults, + fetchNextPage, + hasNextPage, + isFetching, + isFetchingNextPage, + isLoadingError, + } = useInfiniteQuery({ + queryKey: [INFINITE_POST_SCRAPS_QUERY_KEY], + queryFn: async ({ pageParam }) => + await getPostScraps({ params: pageParam }).then((res: any) => res.data), + initialPageParam: { + postScrapId: 0, + scrappedAt: new Date().toISOString().slice(0, 19), + }, + getNextPageParam: (lastPage) => { + if (lastPage.length < POST_SCRAPS_PER_PAGE) { + return undefined; + } + + const lastItem = lastPage[lastPage.length - 1]; + + return { + postScrapId: lastItem.postScrapId, + scrappedAt: lastItem.scrappedAt, + }; + }, + }); + + const openConsultList = useMemo( + () => openConsults?.pages.flatMap((consult) => consult) ?? [], + [openConsults], + ); + + const { observerElem } = useInfiniteObserver({ + fetchNextPage, + hasNextPage, + isFetching, + isFetchingNextPage, + isLoadingError, + }); + + // + // + // + + if (isFetching && !isFetchingNextPage) { + return ( +
+ +
+ ); + } + + if (openConsultList.length === 0) { + return ; + } -interface SavedOpenConsultResultsProps { - openConsultList: openConsultApiObject[]; -} -function SavedOpenConsultResults({ - openConsultList, -}: SavedOpenConsultResultsProps) { return ( {openConsultList.map((card) => ( ))} +
+ ); } @@ -22,7 +105,10 @@ const Wrapper = styled.div` display: flex; flex-direction: column; gap: 1.2rem; + box-sizing: border-box; + padding: 0 2rem; align-items: center; width: 100%; `; + export default SavedOpenConsultResults; diff --git a/src/pages/Buyer/BuyerSavedCounselor.tsx b/src/pages/Buyer/BuyerSavedCounselor.tsx index c3466c46..bad78788 100644 --- a/src/pages/Buyer/BuyerSavedCounselor.tsx +++ b/src/pages/Buyer/BuyerSavedCounselor.tsx @@ -1,21 +1,14 @@ -import { postWishLists } from 'api/post'; import { SavedCounselorResults } from 'components/Buyer/BuyerSavedCounselor.tsx/SavedCounselorResults'; import { BackIcon, HeaderWrapper } from 'components/Buyer/Common/Header'; import { Space } from 'components/Common/Space'; -import { useLayoutEffect, useRef, useState } from 'react'; +import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Grey1 } from 'styles/color'; -import { Body2, Heading } from 'styles/font'; -import { WishlistDataType } from 'utils/type'; -import { ReactComponent as Empty } from 'assets/icons/graphic-noting.svg'; -import styled from 'styled-components'; -import useIntersectionObserver from 'hooks/useIntersectionObserver'; +import { Heading } from 'styles/font'; + import Divider2 from 'components/Common/Divider2'; -import { openConsultApiObject } from './BuyerConsult'; -import { getPostScraps } from 'api/get'; + import SavedOpenConsultResults from 'components/Buyer/BuyerSavedCounselor.tsx/SavedOpenConsultResults'; -import { LoadingSpinner } from 'utils/LoadingSpinner'; -import { Flex } from 'components/Common/Flex'; // // @@ -24,134 +17,13 @@ import { Flex } from 'components/Common/Flex'; export const BuyerSavedCounselor = () => { const navigate = useNavigate(); - const [isInitialLoading, setIsInitialLoading] = useState(true); - const [wishlistData, setWishlistData] = useState([]); - const [openConsultData, setOpenConsultData] = useState< - openConsultApiObject[] - >([]); - - const [isLastElem, setIsLastElem] = useState(false); const [tabState, setTabState] = useState(1); - const preventRef = useRef(true); - - const onIntersect: IntersectionObserverCallback = async (entry) => { - if ( - entry[0].isIntersecting && - !isLastElem && - !isInitialLoading && - preventRef.current - ) { - preventRef.current = false; - if (tabState === 1) { - await fetchWishlistData( - wishlistData[wishlistData.length - 1].wishlistId, - wishlistData[wishlistData.length - 1].updatedAt, - ); - } else if (tabState === 2) { - await fetchOpenConsultData( - openConsultData[openConsultData.length - 1].postScrapId, - openConsultData[openConsultData.length - 1].scrappedAt, - ); - } - - preventRef.current = true; - } - }; - - //현재 대상 및 option을 props로 전달 - const { setTarget } = useIntersectionObserver({ - root: null, - rootMargin: '0px', - threshold: 0.8, - onIntersect, - }); - - /** - * - */ - const fetchWishlistData = async (lastId: number, lastUpdateAt: string) => { - const body = { - wishlistId: lastId, - updatedAt: lastUpdateAt, - }; - try { - const res: any = await postWishLists(body); - if (res.status === 200) { - if (res.data.length !== 0) { - if (lastId === 0) { - setIsInitialLoading(true); - setWishlistData(res.data); - } else { - const updatedReviews = [...wishlistData, ...res.data]; - setWishlistData(updatedReviews); - } - } else { - setIsLastElem(true); - } - } else if (res.response.status !== 401) { - navigate('/mypage'); - } - } catch (e) { - alert(e); - } finally { - if (lastId === 0) { - setIsInitialLoading(false); - } - } - }; - - /** - * - */ - const fetchOpenConsultData = async (lastId: number, lastUpdateAt: string) => { - try { - const params = { - postScrapId: lastId, - scrappedAt: lastUpdateAt, - }; - const res: any = await getPostScraps({ params }); - if (res.status === 200) { - if (res.data.length !== 0) { - if (lastId === 0) { - setIsInitialLoading(true); - setOpenConsultData(res.data); - } else { - const updatedOpenConsultList = [...openConsultData, ...res.data]; - setOpenConsultData(updatedOpenConsultList); - } - } else { - setIsLastElem(true); - } - } else if (res.response.status !== 401) { - // navigate('/mypage'); - } - } catch (err) { - alert(err); - } finally { - if (lastId === 0) { - setIsInitialLoading(false); - } - } - }; - // // // - useLayoutEffect(() => { - setIsInitialLoading(true); - if (tabState === 1) { - fetchWishlistData(0, ''); - } else if (tabState === 2) { - fetchOpenConsultData(0, new Date().toISOString().slice(0, 19)); - } - }, [tabState]); - // - // - // - - if (isInitialLoading) { + if (tabState === 1) { return ( <> @@ -163,121 +35,25 @@ export const BuyerSavedCounselor = () => { 찜 목록 -
- -
+ + ); - } - - if (tabState === 1) { - if (wishlistData.length !== 0) { - return ( - <> - - { - navigate('/mypage'); - }} - /> - 찜 목록 - - - -
- - {!isLastElem ? ( -
- ) : ( -
- )} -
- - ); - } else { - return ( - <> - - { - navigate('/mypage'); - }} - /> - 찜 목록 - - - - - 아직 찜한 마인더가 없어요. - - - 관심 있는 마인더를 찜하고
더욱 편리하게 상담하세요. -
-
- - ); - } } else if (tabState === 2) { - if (openConsultData.length !== 0) { - return ( - <> - - { - navigate('/mypage'); - }} - /> - 찜 목록 - - - - - - {!isLastElem ? ( -
- ) : ( -
- )} - - - ); - } else { - return ( - <> - - { - navigate('/mypage'); - }} - /> - 찜 목록 - - - - - 아직 저장한 공개상담이 없어요. - - - ); - } + return ( + <> + + { + navigate('/mypage'); + }} + /> + 찜 목록 + + + + + + ); } }; -const EmptyWrapper = styled.div` - display: flex; - flex-direction: column; - align-items: center; -`; -const EmptyIcon = styled(Empty)` - margin-top: 20vh; - padding: 4.7rem 4.413rem 4.603rem 4.5rem; -`;