jeremybarbet / react-native-portalize

The simplest way to render anything on top of the rest.
MIT License
335 stars 22 forks source link

Remounting other portals closes other portals #12

Closed MorganTrudeau closed 1 year ago

MorganTrudeau commented 4 years ago

Hello everyone. I am using this library in my app right now and am experiencing what looks like a bug. Tell me your thoughts....

The use case in my app is the following. I have multiple modals on a single screen. Each modal is using Modalize modal in a separate portal. There is one component that remounts when date changes which will remount the Modal and in return the portal as well. The issue is if I have a modal open in a portal and another portal remounts then the current portal closes and the modal does not fire onClose so it cannot reopen.

This only happens if the portal remounting is before the open portal in the DOM. I think portals should be able to remount without affecting other portals.

I have included a demo. In the demo if you click "Open a" modal "A" opens. 4 seconds later I remount modal B by changing the key. Remounting modal B closes modal A. Create a file with following code and import App into the index of a set up react-native project to test! :)

Versions react-native-portalize: 1.0.4 react: 16.8.6 react-native: 0.60.4

import React, { useEffect, useRef, useState } from 'react';
import { Portal, Host } from 'react-native-portalize';
import { Modalize } from 'react-native-modalize';
import {
  Text,
  View,
  TouchableOpacity,
  StyleSheet,
} from 'react-native';

const App = () => {
  return (
    <View style={{ flex: 1 }}>
      <Host>
        <Test />
      </Host>
    </View>
  );
};

const Button = ({ onPress, title }) => (
  <TouchableOpacity onPress={onPress} style={styles.button}>
    <View
      style={{
        alignItems: 'center',
        justifyContent: 'center',
      }}>
      <Text>{title}</Text>
    </View>
  </TouchableOpacity>
);

const Test = () => {
  const [modalAOpen, setModalAOpen] = useState(false);
  const [modalBOpen, setModalBOpen] = useState(false);
  const [bKey, changeBKey] = useState(Math.random());

  return (
    <View style={{ flex: 1, justifyContent: 'center' }}>
      <Button
        onPress={() => {
          setModalAOpen(true);
          setTimeout(() => changeBKey(Math.random()), 4000);
        }}
        title={'Open a'}
      />
      <Button onPress={() => setModalBOpen(true)} title={'Open b'} />

      <Modal
        title={'b'}
        key={bKey}
        visible={modalBOpen}
        onClosed={() => setModalBOpen(false)}>
        <View style={{ flex: 1, backgroundColor: 'red' }} />
      </Modal>

      <Modal
        visible={modalAOpen}
        onClosed={() => setModalAOpen(false)}
        title={'a'}>
        <View style={{ flex: 1, backgroundColor: 'blue' }} />
      </Modal>
    </View>
  );
};

const Modal = props => {
  const hasMounted = useRef();
  const ref = useRef();

  useEffect(() => {
    console.log('mount', props.title);
    return () => {
      console.log('unmount', props.title);
    };
  }, []);

  useEffect(() => {
    if (hasMounted.current) {
      if (props.visible) {
        ref.current?.open();
      } else {
        ref.current?.close();
      }
    } else {
      hasMounted.current = true;
    }
  }, [props.visible]);

  return (
    <Portal>
      <Modalize ref={ref} onClosed={props.onClosed} {...props}>
        {props.children}
      </Modalize>
    </Portal>
  );
};

const styles = StyleSheet.create({
  button: {
    backgroundColor: "#e6e6e6",
    paddingVertical: 12,
    minWidth: 200,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 15,
  },
});

export default App;
MorganTrudeau commented 1 year ago

Fixed in 1.0.7