Open
Description
Description
In one of our scenarios, on a button click we open a web link and come back to the screen from where it was triggered. On coming back to the screen we display a modal which is working correctly in Android but in iOS it is displayed and then dismissed. This behaviour was observed after we upgraded react-native version to 0.78.
Steps to reproduce
Given below is the file in which we see the issue
import { useFocusEffect } from '@react-navigation/native'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { Alert } from '../../../components/alert/alert'
import { AlertContent } from '../../../components/alert/alert-content'
import { AlertTitle } from '../../../components/alert/alert-title'
import { Button } from '../../../components/button/button'
import { LinkText } from '../../../components/link-text/link-text'
import { TranslatedText } from '../../../components/translated-text/translated-text'
import useAccessibilityFocus from '../../../navigation/a11y/use-accessibility-focus'
import { ErrorWithCode } from '../../../services/errors/errors'
import { useFaqLink } from '../../../services/faq-configuration/hooks/use-faq-link'
import { useTestIdBuilder } from '../../../services/test-id/test-id'
import { useTranslation } from '../../../services/translation/translation'
import { useTextStyles } from '../../../theme/hooks/use-text-styles'
import { useTheme } from '../../../theme/hooks/use-theme'
import { spacing } from '../../../theme/spacing'
import {
AA2AcceptTimeout,
AA2BelowMinAge,
AA2BelowMinYearOfBirth,
AA2CardAuthenticityValidationFailed,
AA2CardDeactivated,
AA2CardRemoved,
AA2CardValidationFailed,
AA2ForeignResidency,
AA2InitError,
AA2PseudonymAlreadyInUse,
AA2SetPinTimeout,
AA2Timeout,
} from '../errors'
import { useCloseFlow } from '../hooks/use-close-flow'
import { useHandleErrors } from '../hooks/use-handle-errors'
export type EidErrorAlertProps = {
error: ErrorWithCode | null
onModalIsVisible?: (isVisible: boolean) => void
cancelEidFlowAlertVisible?: boolean
handleUserCancellation?: boolean
inEidFlow?: boolean
// Add Loading Animation to Alert as there is a react native issue with multuple modals
isLoading?: boolean
}
export const EidErrorAlert: React.FC<EidErrorAlertProps> = ({
error,
onModalIsVisible,
cancelEidFlowAlertVisible = false,
handleUserCancellation = false,
inEidFlow = true,
isLoading,
}) => {
const { buildTestId, addTestIdModifier } = useTestIdBuilder()
const testID = buildTestId('eid_error_alert')
const { colors } = useTheme()
const { t } = useTranslation()
const [textStyles] = useTextStyles()
const [focusRef, setFocus] = useAccessibilityFocus()
useFocusEffect(setFocus)
const [intError, setIntError] = useState<ErrorWithCode | null>(null)
useHandleErrors(setIntError, handleUserCancellation, cancelEidFlowAlertVisible, inEidFlow)
const { closeFlow } = useCloseFlow(inEidFlow)
useEffect(() => {
if (error !== null) {
setIntError(error)
}
}, [error])
useEffect(() => {
if (onModalIsVisible !== undefined) {
onModalIsVisible(intError !== null)
}
}, [intError, onModalIsVisible])
const eid_belowMinYearOfBirth_faq_link = useFaqLink('ENTITLED_USER_GROUP')
const handleClose = useCallback(async () => {
await closeFlow()
setIntError(null)
}, [closeFlow])
const errorMessage: string | undefined = useMemo(() => {
if (intError instanceof AA2InitError) {
return t('eid_error_init_message')
} else if (intError instanceof AA2BelowMinYearOfBirth) {
return t('eid_error_belowMinYearOfBirth_message')
} else if (intError instanceof AA2BelowMinAge) {
return t('eid_error_belowMinAge_message')
} else if (intError instanceof AA2ForeignResidency) {
return t('eid_error_foreignResidency_message')
} else if (intError instanceof AA2PseudonymAlreadyInUse) {
return t('eid_error_pseudonymAlreadyInUse_message')
} else if (intError instanceof AA2CardDeactivated) {
return t('eid_error_cardDeactivated_message')
} else if (intError instanceof AA2Timeout) {
return t('eid_error_timeout_message')
} else if (intError instanceof AA2CardRemoved) {
return t('eid_error_cardRemoved_message')
} else if (intError instanceof AA2CardValidationFailed) {
return t('eid_error_cardValidationFailed_message')
} else if (intError instanceof AA2CardAuthenticityValidationFailed) {
return t('eid_error_cardAuthenticityValidationFailed_message')
} else if (intError instanceof AA2AcceptTimeout) {
return t('eid_error_acceptTimeout_message')
} else if (intError instanceof AA2SetPinTimeout) {
return t('eid_error_setPinTimeout_message')
}
}, [intError, t])
const errorCode: string | undefined = useMemo(() => {
if (intError === null) {
return
} else if (!intError.detailCode) {
return intError.errorCode
} else {
return `${intError.errorCode} - ${intError.detailCode}`
}
}, [intError])
console.log('🔍 Alert visible:', intError !== null, 'intError:', intError)
return (
<Alert visible={intError !== null} isLoading={isLoading} dismissable={false}>
<AlertContent ref={focusRef}>
<AlertTitle i18nKey="eid_error_title" testID={addTestIdModifier(testID, 'title')} />
{!errorMessage && (
<TranslatedText
textStyle="BodyRegular"
i18nKey="error_alert_message_fallback"
testID={addTestIdModifier(testID, 'message')}
textStyleOverrides={{ color: colors.labelColor }}
/>
)}
<View style={styles.content}>
{errorMessage ? (
<Text
style={[textStyles.BodyRegular, styles.message, { color: colors.labelColor }]}
testID={addTestIdModifier(testID, 'message_detail')}>
{errorMessage}
</Text>
) : (
<TranslatedText
i18nKey="eid_error_try_again_message"
testID={addTestIdModifier(testID, 'try_again_message')}
textStyle="BodyRegular"
textStyleOverrides={[styles.message, { color: colors.labelColor }]}
/>
)}
<Text
style={[textStyles.BodyRegular, styles.message, { color: colors.labelColor }]}
testID={addTestIdModifier(testID, 'code')}>
{errorCode}
</Text>
{intError?.errorDetails ? (
<Text
style={[textStyles.BodyRegular, styles.message, { color: colors.labelColor }]}
testID={addTestIdModifier(testID, 'details')}>
{intError.errorDetails}
</Text>
) : null}
{intError instanceof AA2BelowMinYearOfBirth && (
<View style={styles.textPadding}>
<LinkText
testID={buildTestId('eid_belowMinYearOfBirth_faq_link')}
i18nKey="eid_belowMinYearOfBirth_faq_link"
link={eid_belowMinYearOfBirth_faq_link}
/>
</View>
)}
</View>
<Button
widthOption="stretch"
variant="primary"
i18nKey="alert_cta"
onPress={handleClose}
testID={addTestIdModifier(testID, 'cta')}
/>
</AlertContent>
</Alert>
)
}
const styles = StyleSheet.create({
message: {
textAlign: 'center',
},
content: {
marginBottom: spacing[6],
gap: spacing[4],
},
textPadding: {
paddingTop: spacing[6],
justifyContent: 'center',
},
})
import React, { useCallback, useMemo } from 'react'
import type { PropsWithChildren } from 'react'
import { Modal, type ModalProps } from 'react-native'
import { LoadingIndicatorOverlay } from '../loading-indicator/loading-indicator-overlay'
import { AlertBackdrop } from './alert-backdrop'
import { AlertContainer } from './alert-container'
import { AlertContextImpl } from './alert-context'
export type AlertProps = ModalProps &
PropsWithChildren<{
visible: boolean
onChange?: (visible: boolean) => void
dismissable?: boolean
// Add Loading Animation to Alert as there is a react native issue with multuple modals
isLoading?: boolean
}>
export const Alert = ({ visible, onChange, children, dismissable, isLoading, ...modalProps }: AlertProps) => {
const onShow = useCallback(() => onChange?.(true), [onChange])
const onHide = useCallback(() => onChange?.(false), [onChange])
const providerValue = useMemo(() => ({ dismiss: onHide }), [onHide])
return (
<AlertContextImpl.Provider value={providerValue}>
<Modal
// DO NOT USE `animationType`
// this leads to a ui issue
// in which the refresh control is not hiding anymore when opening a modal in parallel
// the workaround is to animate the modal on our own
// see `AlertContainer`
presentationStyle="overFullScreen"
transparent={true}
visible={visible || isLoading === true}
onRequestClose={onHide}
onShow={onShow}
onDismiss={onHide}
{...modalProps}>
{isLoading ? (
<LoadingIndicatorOverlay />
) : (
<AlertContainer visible={visible}>
<AlertBackdrop dismissable={dismissable} />
{children}
</AlertContainer>
)}
</Modal>
</AlertContextImpl.Provider>
)
}
React Native Version
0.78.0
Affected Platforms
Runtime - iOS
Areas
Fabric - The New Renderer
Output of npx @react-native-community/cli info
System:
OS: macOS 15.3.1
CPU: (10) arm64 Apple M1 Pro
Memory: 85.89 MB / 16.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 18.18.0
path: ~/.nvm/versions/node/v18.18.0/bin/node
Yarn:
version: 1.22.22
path: ~/.nvm/versions/node/v18.18.0/bin/yarn
npm:
version: 9.8.1
path: ~/.nvm/versions/node/v18.18.0/bin/npm
Watchman:
version: 2025.04.14.00
path: /opt/homebrew/bin/watchman
Managers:
CocoaPods:
version: 1.16.2
path: /Users/I583816/.gem/bin/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 24.2
- iOS 18.2
- macOS 15.2
- tvOS 18.2
- visionOS 2.2
- watchOS 11.2
Android SDK:
API Levels:
- "28"
- "30"
- "31"
- "32"
- "33"
- "33"
- "34"
- "35"
Build Tools:
- 28.0.3
- 29.0.2
- 30.0.3
- 31.0.0
- 32.0.0
- 33.0.0
- 33.0.1
- 33.0.2
- 34.0.0
- 34.0.0
- 34.0.0
- 35.0.0
System Images:
- android-29 | Google Play ARM 64 v8a
- android-30 | Google APIs ARM 64 v8a
- android-31 | Google APIs ARM 64 v8a
- android-31 | Google Play ARM 64 v8a
- android-33 | Google APIs ARM 64 v8a
- android-34 | Google Play ARM 64 v8a
- android-35 | Google APIs ARM 64 v8a
Android NDK: Not Found
IDEs:
Android Studio: 2022.3 AI-223.8836.35.2231.11005911
Xcode:
version: 16.2/16C5032a
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.9
path: /Users/I583816/Library/Java/JavaVirtualMachines/corretto-17.0.9/Contents/Home/bin/javac
Ruby:
version: 3.1.1
path: /Users/I583816/.asdf/shims/ruby
npmPackages:
"@react-native-community/cli":
installed: 15.0.1
wanted: 15.0.1
react:
installed: 19.0.0
wanted: 19.0.0
react-native:
installed: 0.78.0
wanted: 0.78.0
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: false
iOS:
hermesEnabled: true
newArchEnabled: true
Stacktrace or Logs
N/A
MANDATORY Reproducer
https://github.com/vinvijdev/IssueReproducer
Screenshots and Videos
No response