React Native:从另一个 Modal 内部打开一个 Modal 在 iOS 中不起作用

问题描述 投票:0回答:4

我有一个 Dropdown 组件,它只是一个 React Native

Modal
,位于切换开关旁边 -
Modal
允许我将整个背景设置为
Pressable
,这样当按下下拉菜单之外的任何区域时,我可以关闭下拉菜单。

下拉菜单中的每个项目都有一个

onPress
属性,它执行给定的功能,同时关闭下拉菜单本身。这非常有效,除非我想使用
onPress
事件打开另一个反应原生
Modal

这是一个(简化的)示例:

<>
  // 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>
</>

这在网络上按预期工作 - 下拉菜单的

Modal
关闭,另一个
Modal
同时打开。然而,在 iOS 上,第二个
Modal
永远不会打开,并且应用程序实际上变得完全没有响应,直到我从 Metro 构建器重新启动它。

我在 Stack Overflow 上看到其他问题提到“从另一个模态内部打开一个模态”,但现有的问题似乎都与嵌套模态有关。就我而言,我们实际上并没有尝试嵌套模态 - 第二个模态应该在第一个模态关闭时打开。 iOS 应用程序似乎没有渲染第二个模态,即使我可以通过控制台验证

isModalOpen
布尔值是否设置为 true。

我开始认为这实际上是 React Native 本身的一个错误,但我想我应该在这里检查一下,以防它是一个已知问题,也许是事件冒泡之类的问题?

javascript reactjs react-native react-native-web
4个回答
3
投票

这是react-native

中的
已知限制

但作为一种解决方法

  1. 关闭第一个模态后,您可以使用
    setTimeout
    作为第二个模态
  2. 使用条件渲染,以便根据可见性将第二个模式注册(挂载/卸载)到 dom
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;

3
投票

所以归根结底,问题是 React Native 根本不会同时显示两个模态 - 即使您尝试打开一个新模态,而前一个模态的关闭动画正在播放,此限制仍然适用还在完成。

似乎有些人用超时来处理这个问题,但在我的测试中证明这是不可靠的。超时还依赖于一个神奇的数字,而问题的关键是模式在打开新模式之前尚未卸载。

为了解决这个问题,我向下拉菜单的上下文提供程序添加了一个名为

queuedPress
的状态变量,该变量存储刚刚按下的菜单项中的
onPress
函数。我还添加了一个
afterClose
回调,该回调在下拉菜单的关闭动画完成时运行。当按下下拉菜单项时,我存储其
onPress
函数,然后
afterClose
处理实际调用。这可确保
onPress
在动画完成时一直排队,因此从
onPress
内打开的模式将保证在下拉菜单关闭后打开。

根据您的代码,您的实现可能会有很大差异,但就我而言,这是另一种情况,

useContext
拯救了这一天。任何将开放模态数量限制为 1 的解决方案都应该有效。


1
投票

您可以在第一个模态

InteractionManager
中使用
useEffect
。这保证了你的第二个模态将在第一个模态动画完成后打开。

InteractionManager.runAfterInteractions(() => {
   // ...long-running synchronous task...
 });

更详细: https://reactnative.dev/docs/interactionmanager


1
投票

我们可以在第一个模态上使用 onModalHide 属性来触发第二个模态的打开,而不是使用超时来触发第二个模态的打开。

  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>
)

仅在第一个模态框关闭后才会调用 onModalHide 方法。

但是,这并不包含来自react-native的标准Modal。我们需要使用react-native-modal包来实现此功能。它只提供额外的功能,所以应该不难实现。

© www.soinside.com 2019 - 2024. All rights reserved.