I have a Dropdown ponent which is just a React Native Modal
positioned next to a toggle - the Modal
allows me to make the whole background a Pressable
so I can close the dropdown when any area outside it is pressed.
The items inside the dropdown menu each have an onPress
prop which performs a given function while also closing the dropdown itself. This works great, except when I want to use the onPress
event to open another react-native Modal
.
Here's a (simplified) example:
<>
// Custom ponent that renders a react-native Modal
<Dropdown
items={[
{ label: "Press to open a Modal", onPress: () => setIsModalOpen(true) }
]}
/>
// Another react-native Modal
<Modal visible={isModalOpen}>
...
</Modal>
</>
This works as expected on the web - the Dropdown's Modal
closes and the other Modal
opens at the same time. However, on iOS, the second Modal
never opens, and the app actually bees pletely unresponsive until I restart it from the Metro builder.
I've seen other questions on Stack Overflow that mention "opening a modal from inside another modal", but the existing questions all seem to concern nested modals. In my case, we aren't actually trying to nest modals - the second modal should open as the first one closes. The iOS app seems to just not render the second modal, even though I can verify through the console that the isModalOpen
boolean is getting set to true.
I'm beginning to think this is actually a bug with React Native itself, but figured I'd check here in case it's a known issue, maybe with event bubbling or something?
I have a Dropdown ponent which is just a React Native Modal
positioned next to a toggle - the Modal
allows me to make the whole background a Pressable
so I can close the dropdown when any area outside it is pressed.
The items inside the dropdown menu each have an onPress
prop which performs a given function while also closing the dropdown itself. This works great, except when I want to use the onPress
event to open another react-native Modal
.
Here's a (simplified) example:
<>
// Custom ponent that renders a react-native Modal
<Dropdown
items={[
{ label: "Press to open a Modal", onPress: () => setIsModalOpen(true) }
]}
/>
// Another react-native Modal
<Modal visible={isModalOpen}>
...
</Modal>
</>
This works as expected on the web - the Dropdown's Modal
closes and the other Modal
opens at the same time. However, on iOS, the second Modal
never opens, and the app actually bees pletely unresponsive until I restart it from the Metro builder.
I've seen other questions on Stack Overflow that mention "opening a modal from inside another modal", but the existing questions all seem to concern nested modals. In my case, we aren't actually trying to nest modals - the second modal should open as the first one closes. The iOS app seems to just not render the second modal, even though I can verify through the console that the isModalOpen
boolean is getting set to true.
I'm beginning to think this is actually a bug with React Native itself, but figured I'd check here in case it's a known issue, maybe with event bubbling or something?
Share asked Apr 27, 2022 at 21:49 Keith PickeringKeith Pickering 7862 gold badges11 silver badges25 bronze badges 2- 1 This’s a limitation in react native, there’s actually a workaround: using setTimout for opening 2nd modal after closing 1st open which mean that you have to close 1st modal first or wait until it got dismissed than open the 2nd one github./react-native-modal/… – Ibrahim Commented Apr 27, 2022 at 23:03
- With the timeout I'm able to get the second modal to open one time, but subsequent opens fail. Can't seem to get it working consistently. – Keith Pickering Commented Apr 27, 2022 at 23:54
4 Answers
Reset to default 3This's a known limitation in react-native
but as a workaround
- you can use
setTimeout
for the 2nd modal after closing the 1st modal - use conditional rendering so it registers(mount/unmount) to the dom the 2nd modal based on the visibility
import React, {useCallback, useState} from 'react';
import {Button, Modal, Text, View} from 'react-native';
const App = () => {
const [is1stModalVisible, setIs1stModalVisible] = useState(false);
const [is2ndModalVisible, setIs2ndModalVisible] = useState(false);
const onOpen2ndModal = useCallback(() => {
// closes the 1st modal
setIs1stModalVisible(false);
// open the 2nd modal
setTimeout(
() => {
setIs2ndModalVisible(true);
},
// any small number will do, maybe animation duration
100,
);
}, []);
return (
<View>
<Button
title="Open 1st modal"
onPress={() => setIs1stModalVisible(true)}
/>
<Modal visible={is1stModalVisible}>
<Text>Modal 1 content</Text>
<Button title="Open 2nd modal" onPress={onOpen2ndModal} />
</Modal>
{is2ndModalVisible ? (
<Modal visible={is2ndModalVisible}>
<Text>Modal 2 content</Text>
<Button
title="Close 2nd modal"
onPress={() => setIs2ndModalVisible(false)}
/>
</Modal>
) : null}
</View>
);
};
export default App;
So at the end of the day, the issue is that React Native simply won't show two modals at the same time - this limitation applies even if you're trying to open a new modal while the previous one's closing animation is still finishing up.
It seems like some people handle this with a timeout, but that proved unreliable in my testing. A timeout also relies on a magic number, when the crux of the issue is that the modal has yet to unmount before opening a new one.
To solve this, I added a state variable called queuedPress
to my Dropdown menu's context provider that stores the onPress
function from the menu item that was just pressed. I also added an afterClose
callback that runs when the Dropdown menu's closing animation pletes. When a Dropdown item is pressed, I store its onPress
function, then afterClose
handles the actual call. This ensures the onPress
is queued for as long as the animation takes to plete, so the modal opened from within that onPress
will be guaranteed to open after the dropdown is already closed.
Depending on your code your implementation might vary wildly, but in my case this is another situation where useContext
has saved the day. Any solution that limits the number of open modals to 1 should work.
You can use InteractionManager
inside your first modal useEffect
. that guarantees your second modal will open after the first modal animation is finished.
InteractionManager.runAfterInteractions(() => {
// ...long-running synchronous task...
});
more detail: https://reactnative.dev/docs/interactionmanager
Instead of triggering the second modal's opening using a timeout, we could use onModalHide attribute on the first modal to trigger the opening of the second modal.
const [showModal1, setShowModal1] = useState(false);
const [modal2Requested, setModal2Requested] = useState(false);
const [showModal2, setShowModal2] = useState(false);
return (
<View>
<ReactNativeModal
isVisible={showModal1}
onModalHide={() => {
if (modal2Requested) {
setShowModal2(true);
setModal2Requested(false);
}
}}
>
<View>
<Text>Modal 1</Text>
<TouchableOpacity
onPress={() => {
// close this modal and request for second one
setShowModal1(false);
setModal2Requested(true);
}}
>
<Text>Close Modal</Text>
</TouchableOpacity>
</View>
</ReactNativeModal>
<ReactNativeModal isVisible={showModal2}>
<View>
<Text>Modal 2 </Text>
</View>
</ReactNativeModal>
</View>
)
The onModalHide method is called only after the first modal is closed.
However, this does not e with standard Modal from react-native. We need to use the react-native-modal package for this feature. It only provides additional features, so should not be hard to implement.