Home > Blockchain >  React Native: Opening a Modal from inside another Modal doesn't work in iOS
React Native: Opening a Modal from inside another Modal doesn't work in iOS

Time:04-29

I have a Dropdown component 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 component 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 becomes completely 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?

CodePudding user response:

This's a known limitation in react-native

but as a workaround

  1. you can use setTimeout for the 2nd modal after closing the 1st modal
  2. 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;

CodePudding user response:

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 completes. 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 complete, 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.

  • Related