diff --git a/docs/IOS_MODAL_NESTING_ISSUES.md b/docs/IOS_MODAL_NESTING_ISSUES.md new file mode 100644 index 000000000..bad3ff0c2 --- /dev/null +++ b/docs/IOS_MODAL_NESTING_ISSUES.md @@ -0,0 +1,103 @@ +# iOS Nested Modal Issues and Solutions + +## Problem Overview + +In React Native and Expo applications, you may encounter an issue on iOS where opening a modal from within another modal fails to display the second modal correctly. Typically, the second modal only appears after the first one is closed, or it may not appear at all (UI freeze). + +This behavior is primarily due to the way iOS handles `UIViewController` presentations. A `UIViewController` that is already presenting another view controller cannot present a new one on top of the currently presented one. + +## Common Symptoms + +- **Modal B doesn't open**: Pressing a button in Modal A that should open Modal B does nothing. +- **Delayed opening**: Modal B only appears _after_ Modal A is dismissed. +- **UI Freeze**: The application becomes unresponsive because a native layer is waiting for an action that is obscured. + +--- + +## Recommended Solutions + +### 1. Sequential Modal Management (The "Close-then-Open" Pattern) + +Instead of nesting modals, ensure that the first modal is completely closed before the second one is triggered. + +#### How to implement: + +Use a small delay or a callback that fires when the first modal has finished its closing animation. + +```tsx +const [isModalAVisible, setIsModalAVisible] = useState(false); +const [isModalBVisible, setIsModalBVisible] = useState(false); + +const openModalB = () => { + // 1. Close Modal A + setIsModalAVisible(false); + + // 2. Open Modal B after Modal A's transition is complete + // On iOS, a small timeout or requestAnimationFrame is often necessary + setTimeout(() => { + setIsModalBVisible(true); + }, 300); // 300ms is usually enough for the default slide animation +}; +``` + +### 2. Using `onDismiss` (Native Callback) + +React Native's `Modal` component has an `onDismiss` prop (iOS only) that is called when the modal has been dismissed. + +```tsx +const [isModalAVisible, setIsModalAVisible] = useState(false); +const [shouldOpenModalB, setShouldOpenModalB] = useState(false); + +const handleCloseAAndOpenB = () => { + setShouldOpenModalB(true); + setIsModalAVisible(false); +}; + +return ( + <> + { + if (shouldOpenModalB) { + setIsModalBVisible(true); + setShouldOpenModalB(false); + } + }} + > +