diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index 4285b95..5291845 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -1,10 +1,11 @@ import 'react-native-gesture-handler'; -import { QueryClientProvider } from '@tanstack/react-query'; +import { focusManager, QueryClientProvider } from '@tanstack/react-query'; import { useFonts } from 'expo-font'; import { Stack } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import { useEffect } from 'react'; +import { AppState } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { fontFamilyWeight } from '@/constants/typography'; @@ -31,6 +32,14 @@ export default function RootLayout() { if (error) throw error; }, [error]); + useEffect(() => { + const subscription = AppState.addEventListener('change', (state) => { + focusManager.setFocused(state === 'active'); + }); + + return () => subscription.remove(); + }, []); + useEffect(() => { const initializeApp = async () => { try { diff --git a/src/domains/measurement/api/queries.ts b/src/domains/measurement/api/queries.ts index 92dd679..cc394c2 100644 --- a/src/domains/measurement/api/queries.ts +++ b/src/domains/measurement/api/queries.ts @@ -15,11 +15,8 @@ import type { UseQueryOptions } from '@tanstack/react-query'; type MeasurementQueryOptions = Omit, 'queryKey' | 'queryFn'>; -/** - * 지난 날짜 기록은 세션 중 재방문해도 다시 부르지 않는다. - * 데일리↔위클리↔먼슬리 반복 이동 시 과거 날짜 재요청을 막는 핵심. - */ -const PAST_DATE_STALE_TIME = Infinity; +/** 과거 기록도 다른 기기에서 변경될 수 있으므로 화면 재진입·앱 복귀 시 재검증합니다. */ +const PAST_DATE_STALE_TIME = 0; /** * 오늘/이번 주·월 기록은 하루 중 바뀔 수 있으나, 갱신은 시간 폴링이 아니라 * 컨디션·수면 기록 mutation의 invalidate로 처리한다. diff --git a/src/lib/api/query-client.ts b/src/lib/api/query-client.ts index 3b28020..e74a662 100644 --- a/src/lib/api/query-client.ts +++ b/src/lib/api/query-client.ts @@ -4,7 +4,9 @@ export const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 2, - staleTime: 1000 * 60 * 5, + // 서버 상태는 화면 재진입·앱 복귀 시 재검증합니다. 각 도메인의 명시적인 + // staleTime은 해당 데이터의 갱신 특성에 따라 별도로 유지합니다. + staleTime: 0, }, }, }); diff --git a/src/screens/schedule/card-list/hooks/use-card-list-screen.ts b/src/screens/schedule/card-list/hooks/use-card-list-screen.ts index 456659f..239e442 100644 --- a/src/screens/schedule/card-list/hooks/use-card-list-screen.ts +++ b/src/screens/schedule/card-list/hooks/use-card-list-screen.ts @@ -111,10 +111,14 @@ export function useCardListScreen() { }); const schedules = useMemo( () => [ - ...(pinSearchQuery.data?.pages.flatMap((page) => page.schedules) ?? []), - ...(queueSearchQuery.data?.pages.flatMap((page) => page.schedules) ?? []), + ...(isPinQueryEnabled + ? (pinSearchQuery.data?.pages.flatMap((page) => page.schedules) ?? []) + : []), + ...(isQueueQueryEnabled + ? (queueSearchQuery.data?.pages.flatMap((page) => page.schedules) ?? []) + : []), ], - [pinSearchQuery.data, queueSearchQuery.data], + [isPinQueryEnabled, isQueueQueryEnabled, pinSearchQuery.data, queueSearchQuery.data], ); const cards = useMemo( () => toCardItemsFromScheduleList(schedules, personalTags), @@ -135,18 +139,25 @@ export function useCardListScreen() { const handleSearchClear = useCallback(() => { router.setParams({ q: '' }); }, []); - const hasNextPage = pinSearchQuery.hasNextPage || queueSearchQuery.hasNextPage; + const hasNextPage = + (isPinQueryEnabled && pinSearchQuery.hasNextPage) || + (isQueueQueryEnabled && queueSearchQuery.hasNextPage); const isFetchingNextPage = - pinSearchQuery.isFetchingNextPage || queueSearchQuery.isFetchingNextPage; + (isPinQueryEnabled && pinSearchQuery.isFetchingNextPage) || + (isQueueQueryEnabled && queueSearchQuery.isFetchingNextPage); const fetchNextPage = useCallback(() => { - if (pinSearchQuery.hasNextPage && !pinSearchQuery.isFetchingNextPage) { + if (isPinQueryEnabled && pinSearchQuery.hasNextPage && !pinSearchQuery.isFetchingNextPage) { void pinSearchQuery.fetchNextPage(); } - if (queueSearchQuery.hasNextPage && !queueSearchQuery.isFetchingNextPage) { + if ( + isQueueQueryEnabled && + queueSearchQuery.hasNextPage && + !queueSearchQuery.isFetchingNextPage + ) { void queueSearchQuery.fetchNextPage(); } - }, [pinSearchQuery, queueSearchQuery]); + }, [isPinQueryEnabled, isQueueQueryEnabled, pinSearchQuery, queueSearchQuery]); const handleScroll = useCardListInfiniteScroll({ hasNextPage, isFetchingNextPage, @@ -162,8 +173,8 @@ export function useCardListScreen() { sections, hasActiveFilter, totalCards: - (pinSearchQuery.data?.pages[0]?.totalElements ?? 0) + - (queueSearchQuery.data?.pages[0]?.totalElements ?? 0), + (isPinQueryEnabled ? (pinSearchQuery.data?.pages[0]?.totalElements ?? 0) : 0) + + (isQueueQueryEnabled ? (queueSearchQuery.data?.pages[0]?.totalElements ?? 0) : 0), periodLabel: formatCardListPeriodLabel(filters.startDate, filters.endDate), isLoading: (isPinQueryEnabled && pinSearchQuery.isLoading) ||