From f3d9f9167510cbc10b7437654326d478a7c39d8f Mon Sep 17 00:00:00 2001 From: tsyirvo Date: Sat, 25 Oct 2025 10:05:43 +0200 Subject: [PATCH] feat: add utils to refresh stale queries --- CLAUDE.md | 4 +-- src/app/_layout.tsx | 5 +-- src/infra/api/hooks/index.ts | 2 ++ src/infra/api/hooks/useAppFocusManager.ts | 21 +++++++++++++ src/infra/api/hooks/useRefreshOnFocus.ts | 37 +++++++++++++++++++++++ src/infra/api/index.ts | 1 + 6 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/infra/api/hooks/index.ts create mode 100644 src/infra/api/hooks/useAppFocusManager.ts create mode 100644 src/infra/api/hooks/useRefreshOnFocus.ts create mode 100644 src/infra/api/index.ts diff --git a/CLAUDE.md b/CLAUDE.md index 2e26ddac..a4903897 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -122,9 +122,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### React Component Structure -- Use arrow functions with extracted Props type +- Use arrow functions with extracted Props interfaces - Don't use `React.FC` type -- Prefer types to interfaces for props +- Prefer interfaces to types for Props - To extract Props type, use `ComponentProps` - No need to forwardRef, as the project uses React 19 diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index d43afd70..396a8d94 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -1,5 +1,6 @@ import '@formatjs/intl-getcanonicallocales/polyfill'; import 'intl-pluralrules'; +import '../infra/i18n'; import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'; import * as Sentry from '@sentry/react-native'; @@ -19,6 +20,7 @@ import { useAppScreenTracking, useAppStateTracking, } from '$features/navigation'; +import { useAppFocusManager } from '$infra/api'; import { persistOptions, queryClient } from '$infra/api/queryClient'; import { ErrorMonitoring } from '$infra/monitoring'; import { ProductTrackingProvider } from '$infra/productTracking'; @@ -35,8 +37,6 @@ import { useRoutingInstrumentation, } from '$shared/hooks'; -import '../infra/i18n'; - // Sentry is initialized here so that it runs before Sentry.wrap() ErrorMonitoring.init(); @@ -62,6 +62,7 @@ const RootLayout = () => { useCheckNetworkStateOnMount(); useAppStateTracking(); useAppScreenTracking(); + useAppFocusManager(); return ( diff --git a/src/infra/api/hooks/index.ts b/src/infra/api/hooks/index.ts new file mode 100644 index 00000000..43f6a8cf --- /dev/null +++ b/src/infra/api/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './useAppFocusManager'; +export * from './useRefreshOnFocus'; diff --git a/src/infra/api/hooks/useAppFocusManager.ts b/src/infra/api/hooks/useAppFocusManager.ts new file mode 100644 index 00000000..a6383e5d --- /dev/null +++ b/src/infra/api/hooks/useAppFocusManager.ts @@ -0,0 +1,21 @@ +import { focusManager } from '@tanstack/react-query'; +import type { AppStateStatus } from 'react-native'; +import { AppState, Platform } from 'react-native'; + +import { useRunOnMount } from '$shared/hooks'; + +export const useAppFocusManager = () => { + useRunOnMount(() => { + const onAppStateChange = (status: AppStateStatus) => { + if (Platform.OS !== 'web') { + focusManager.setFocused(status === 'active'); + } + }; + + const subscription = AppState.addEventListener('change', onAppStateChange); + + return () => { + subscription.remove(); + }; + }); +}; diff --git a/src/infra/api/hooks/useRefreshOnFocus.ts b/src/infra/api/hooks/useRefreshOnFocus.ts new file mode 100644 index 00000000..80dcc9ae --- /dev/null +++ b/src/infra/api/hooks/useRefreshOnFocus.ts @@ -0,0 +1,37 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { useFocusEffect } from 'expo-router'; +import { useCallback, useRef } from 'react'; + +interface UseRefreshOnFocusOptions { + shouldRefetchAll?: boolean; +} + +export const useRefreshOnFocus = (options?: UseRefreshOnFocusOptions) => { + const { shouldRefetchAll = false } = options ?? {}; + + const isFirstTimeRef = useRef(true); + + const queryClient = useQueryClient(); + + useFocusEffect( + useCallback(() => { + // Skip the first focus to avoid double-fetching on initial mount + if (isFirstTimeRef.current) { + isFirstTimeRef.current = false; + + return; + } + + if (shouldRefetchAll) { + void queryClient.refetchQueries({ + type: 'active', + }); + } else { + void queryClient.refetchQueries({ + stale: true, + type: 'active', + }); + } + }, [queryClient, shouldRefetchAll]), + ); +}; diff --git a/src/infra/api/index.ts b/src/infra/api/index.ts new file mode 100644 index 00000000..4cc90d02 --- /dev/null +++ b/src/infra/api/index.ts @@ -0,0 +1 @@ +export * from './hooks';