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