From 7e5c4eeb813ae70a61c3ff9dfd4f23b86efe8c72 Mon Sep 17 00:00:00 2001
From: Jan Ole Schmidt <jan.ole.schmidt@holi.team>
Date: Thu, 27 Mar 2025 12:13:05 +0000
Subject: [PATCH] HOLI-11257: invalidate cache for the challenges carousel on
 the home feed

---
 core/screens/homeFeed/Feed.tsx                | 16 +++-
 .../components/feed/ChallengesCarousel.tsx    | 79 +++++++++++--------
 2 files changed, 62 insertions(+), 33 deletions(-)

diff --git a/core/screens/homeFeed/Feed.tsx b/core/screens/homeFeed/Feed.tsx
index 3c207b4b56..9a262c3d49 100644
--- a/core/screens/homeFeed/Feed.tsx
+++ b/core/screens/homeFeed/Feed.tsx
@@ -38,6 +38,12 @@ import { useFeedStats } from '@holi/core/screens/homeFeed/helpers/useFeedStats'
 import ChallengesCarousel from '@holi-apps/challenges/components/feed/ChallengesCarousel'
 const surveyUrl = 'https://tally.so/r/nrdGxX'
 
+const MemoizedChallengesCarousel = memo(({ refreshing }: { refreshing: boolean }) => {
+  return <ChallengesCarousel refreshing={refreshing} />
+})
+
+MemoizedChallengesCarousel.displayName = 'MemoizedChallengesCarousel'
+
 const HomeFeed = () => {
   const { t } = useTranslation()
   const { theme } = useTheme()
@@ -55,6 +61,12 @@ const HomeFeed = () => {
   const { track } = useTracking()
   const navigation = useNavigation()
   const ref = useRef(null)
+  const refreshingRef = useRef(false)
+
+  // Update ref when refreshing changes
+  useEffect(() => {
+    refreshingRef.current = refreshing
+  }, [refreshing])
 
   const keyExtractor = useCallback(
     (
@@ -118,11 +130,11 @@ const HomeFeed = () => {
       <HoliTransition.FadeDown visible>
         <View style={styles.listHeader}>
           <HomeFeedHeader />
-          <ChallengesCarousel />
+          <MemoizedChallengesCarousel refreshing={refreshing} />
         </View>
       </HoliTransition.FadeDown>
     ),
-    []
+    [refreshing]
   )
 
   const listItemTrackingEvent: ListItemTrackingEvent<FeedItem> = (item) => {
diff --git a/holi-apps/challenges/components/feed/ChallengesCarousel.tsx b/holi-apps/challenges/components/feed/ChallengesCarousel.tsx
index 69bf8adb6f..582b79b815 100644
--- a/holi-apps/challenges/components/feed/ChallengesCarousel.tsx
+++ b/holi-apps/challenges/components/feed/ChallengesCarousel.tsx
@@ -5,7 +5,6 @@ import { createInAppChallengesPath } from '@holi-apps/challenges/helpers/navigat
 import { useChallengesHeaders } from '@holi-apps/challenges/helpers/useChallengesHeaders'
 import ChallengesSlider from '@holi-apps/challenges/components/feed/ChallengesSlider'
 import { logError } from '@holi/core/errors/helpers'
-import { useOnRefresh } from '@holi/core/refreshing/hooks/useOnRefresh'
 import { TrackingEvent } from '@holi/core/tracking'
 import type { ListItemIdentifier, ListItemTrackingEvent } from '@holi/core/tracking/TrackableFlatList.shared'
 import { withProfiler } from '@sentry/react'
@@ -20,44 +19,62 @@ import { HoliGap } from '@holi/ui/components/atoms/HoliGap'
 const API_URL = challengeAppBaseUrl + '/api/v1/recommended-challenges'
 const CARD_WIDTH = 130
 
-const ChallengesCarousel = () => {
-  const [isLoading, setIsLoading] = useState(true)
+interface ChallengesCarouselProps {
+  refreshing?: boolean
+}
+
+const ChallengesCarousel = ({ refreshing }: ChallengesCarouselProps) => {
+  const [isInitialLoading, setIsInitialLoading] = useState(true)
   const [challenges, setChallenges] = useState<ChallengeTeaser[]>([])
   const { t } = useTranslation()
   const headers = useChallengesHeaders()
 
-  const fetchChallenges = useCallback(async () => {
-    if (!headers) {
-      return
-    }
+  const fetchChallenges = useCallback(
+    async (showLoading = false) => {
+      if (!headers) {
+        return
+      }
 
-    try {
-      setIsLoading(true)
-      const apiHeaders = new Headers(headers)
-      apiHeaders.append('Accept', 'application/json')
-      const response = await fetch(API_URL, { headers: apiHeaders })
+      try {
+        if (showLoading) {
+          setIsInitialLoading(true)
+        }
 
-      if (!response.ok) {
-        throw new Error('Failed to fetch recommended challenges')
-      }
+        const apiHeaders = new Headers(headers)
+        apiHeaders.append('Accept', 'application/json')
+        const response = await fetch(API_URL, { headers: apiHeaders })
 
-      const data = await response.json()
-      setChallenges(data)
-    } catch (error) {
-      logError(error, 'Error fetching challenges', {
-        location: 'RecommendedChallenges.fetchChallenges',
-      })
-      setChallenges([])
-    } finally {
-      setIsLoading(false)
-    }
-  }, [headers])
+        if (!response.ok) {
+          throw new Error('Failed to fetch recommended challenges')
+        }
 
+        const data = await response.json()
+        setChallenges(data)
+      } catch (error) {
+        logError(error, 'Error fetching challenges', {
+          location: 'RecommendedChallenges.fetchChallenges',
+        })
+        setChallenges([])
+      } finally {
+        if (showLoading) {
+          setIsInitialLoading(false)
+        }
+      }
+    },
+    [headers]
+  )
+
+  // Initial fetch on mount - show loading state
   useEffect(() => {
-    fetchChallenges()
+    fetchChallenges(true)
   }, [fetchChallenges])
 
-  useOnRefresh(fetchChallenges)
+  // Fetch silently when parent is refreshing
+  useEffect(() => {
+    if (refreshing) {
+      fetchChallenges(false)
+    }
+  }, [refreshing, fetchChallenges])
 
   const listItemTrackingEvent: ListItemTrackingEvent<Challenge> = ({ title, id }) => {
     return TrackingEvent.RecommendationViewed('challenge', { title, id })
@@ -67,8 +84,8 @@ const ChallengesCarousel = () => {
     return id
   }
 
-  // show skeleton while loading
-  if (isLoading) {
+  // Show skeleton only during initial loading
+  if (isInitialLoading) {
     return (
       <Stack direction="column" gap="3xs">
         <HoliSkeleton height={Spacing.xl} width={180} borderRadius={16} />
@@ -80,7 +97,7 @@ const ChallengesCarousel = () => {
     )
   }
 
-  // show nothing if there are no challenges
+  // Show nothing if there are no challenges
   if (!challenges || challenges.length === 0) {
     return null
   }
-- 
GitLab