diff --git a/CHANGELOG.md b/CHANGELOG.md index 361d5d09506..00d9d8d2dd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - added: Moonpay Sell support for ACH. - added: Maestro `testID` support in `NotificationCard` - changed: Scanning private key button copy updated from "Import" -> "Confirm" +- fixed: iOS not immediately showing `EdgeCarousel` cards if only one card is present - fixed: `SceneWrapper` bottom inset calculations for scenes that do not `avoidKeyboard` ## 4.26.0 (2025-04-14) diff --git a/src/components/common/EdgeCarousel.tsx b/src/components/common/EdgeCarousel.tsx index 70f538652d4..5d5a93baf7f 100644 --- a/src/components/common/EdgeCarousel.tsx +++ b/src/components/common/EdgeCarousel.tsx @@ -1,7 +1,8 @@ import React, { useState } from 'react' -import { ListRenderItem, View } from 'react-native' +import { InteractionManager, ListRenderItem, Platform, View } from 'react-native' import Carousel, { Pagination } from 'react-native-snap-carousel' +import { useAsyncEffect } from '../../hooks/useAsyncEffect' import { useHandler } from '../../hooks/useHandler' import { cacheStyles, Theme, useTheme } from '../services/ThemeContext' @@ -26,16 +27,44 @@ export function EdgeCarousel(props: Props): JSX.Element { const carouselRef = React.useRef>(null) const [activeIndex, setActiveIndex] = useState(0) + const [dataLocal, setDataLocal] = useState(data) + + React.useEffect(() => { + setDataLocal(data) + }, [data]) const renderItem = useHandler>(info => ( {props.renderItem(info)} )) + /** + * Carousel's FlatList bug workaround. Fixes the issue where items are + * hidden until scroll actions are performed either in the carousel or on the + * scene itself. + */ + useAsyncEffect( + async () => { + // HACK: With 1 item, this is the only way to force a render in iOS + if (Platform.OS === 'ios' && dataLocal.length === 1) { + setDataLocal([]) + setTimeout(() => setDataLocal(data), 50) + } + // The built-in hack fn works for all other cases + else if (carouselRef.current != null) { + await InteractionManager.runAfterInteractions(() => { + carouselRef.current?.triggerRenderingHack() + }) + } + }, + [data], + 'triggerRenderingHack' + ) + return ( (props: Props): JSX.Element { marginTop: -theme.rem(1), marginBottom: -theme.rem(1) }} - dotsLength={data.length} + dotsLength={dataLocal.length} activeDotIndex={activeIndex} tappableDots={carouselRef.current != null} dotStyle={styles.dotStyle}