diff --git a/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx b/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx
index 859c858f..6033d464 100644
--- a/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx
+++ b/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx
@@ -1,9 +1,7 @@
-import {useEffect, useState} from 'react';
-import {useInView} from 'react-intersection-observer';
-
import styles from './MySpaceList.module.scss';
import {useGetSpaces, useGetSpacesOut} from '@/hooks/Spaces/useSpaces';
+import {useInfiniteScroll} from '@/hooks/useInfiniteScroll';
import ObserveTarget from '@/components/Route/ObserveTarget/ObserveTarget';
@@ -15,27 +13,7 @@ import {MySpaceListProps} from '@/types/user';
function MySpaceList({tab}: MySpaceListProps) {
const {data: upcomingData} = useGetSpaces(true);
- const {data: outdatedData, fetchNextPage} = useGetSpacesOut();
- const [hasNextData, setHasNextData] = useState(true);
- const {ref: inViewRef, inView} = useInView({
- rootMargin: '-56px',
- threshold: 0.8,
- });
-
- useEffect(() => {
- // inView true + next pages exist
- if (outdatedData) {
- const {last} = outdatedData.pages.at(-1).data;
-
- if (inView && !last) {
- fetchNextPage();
- }
- if (last) {
- setHasNextData(false);
- }
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [inView]);
+ const [outdatedData, hasNextData, inViewRef] = useInfiniteScroll(useGetSpacesOut);
return (
diff --git a/src/hooks/useInfiniteScroll.ts b/src/hooks/useInfiniteScroll.ts
new file mode 100644
index 00000000..c55bf884
--- /dev/null
+++ b/src/hooks/useInfiniteScroll.ts
@@ -0,0 +1,29 @@
+import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query';
+import {useEffect, useState} from 'react';
+import {useInView} from 'react-intersection-observer';
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+export const useInfiniteScroll = (infiniteQueryFn: () => UseInfiniteQueryResult, Error>) => {
+ const {data, fetchNextPage} = infiniteQueryFn();
+ const [hasNextData, setHasNextData] = useState(true);
+ const {ref: inViewRef, inView} = useInView({
+ rootMargin: '-40px',
+ threshold: 0.8,
+ });
+
+ useEffect(() => {
+ if (data) {
+ const {last} = data.pages.at(-1).data;
+
+ if (inView && !last) {
+ fetchNextPage();
+ }
+ if (last) {
+ setHasNextData(false);
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [inView]);
+
+ return [data, hasNextData, inViewRef] as const;
+};
diff --git a/src/pages/User/MyReview/MyReview.tsx b/src/pages/User/MyReview/MyReview.tsx
index e64804c5..701d7c26 100644
--- a/src/pages/User/MyReview/MyReview.tsx
+++ b/src/pages/User/MyReview/MyReview.tsx
@@ -1,9 +1,9 @@
import {useDisclosure} from '@chakra-ui/react';
-import {useEffect, useRef, useState} from 'react';
-import {useInView} from 'react-intersection-observer';
+import {useRef} from 'react';
import styles from './MyReview.module.scss';
+import {useInfiniteScroll} from '@/hooks/useInfiniteScroll';
import {useGetMyReview} from '@/hooks/User/useMyReview';
import Header from '@/components/Auth/Header/Header';
@@ -21,27 +21,9 @@ import {Reviews} from '@/types/myReview';
function MyReview() {
const {isOpen: isBottomSlideOpen, onOpen: onBottomSlideOpen, onClose: onBottomSlideClose} = useDisclosure();
- const {data: reviews, fetchNextPage} = useGetMyReview();
- const [hasNextData, setHasNextData] = useState(true);
- const {ref: inViewRef, inView} = useInView({
- rootMargin: '-40px',
- threshold: 0.8,
- });
- const clickedReviewId = useRef();
+ const [reviews, hasNextData, inViewRef] = useInfiniteScroll(useGetMyReview);
- useEffect(() => {
- if (reviews) {
- const {last} = reviews.pages.at(-1).data;
-
- if (inView && !last) {
- fetchNextPage();
- }
- if (last) {
- setHasNextData(false);
- }
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [inView]);
+ const clickedReviewId = useRef();
return (