Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multiple modals not working #171

Closed
macrozone opened this issue Jun 13, 2018 · 26 comments
Closed

multiple modals not working #171

macrozone opened this issue Jun 13, 2018 · 26 comments
Labels

Comments

@macrozone
Copy link

I switched from react-native's default Modal to react-native-modal and i noticed that if I have multiple modals open at the same time, only one will ever be visible.

react-native's modal handles this correctly: they display only the first one, but if i dismiss, i see the other modals.

With react-native-modal, the others are simply ignored, altough they are mounted with isVisible={true}. This leads to inconsistency.

@mmazzarolo
Copy link
Member

mmazzarolo commented Jun 13, 2018

Hi!
Unfortunately it is no possible showing more than a modal at the same time right now because react native's default modal do not support this feature.

There should be a few GitHub issues both here and in the react-native repo discussing the subject. It happens even with dialogs/alerts.

If, instead of showing the modals at the same time, you just want to show a modal one after another you should keep in mind that you should wait for the animation to complete before showing it (both with this modal with the original one).

TLDR:
Show the new second modal using the onModalHide prop of the first one.

Let me know if you need further info!

@macrozone
Copy link
Author

hey @mmazzarolo thanks for your answer.

i think you misunderstand my issue, so let me explain:

  • react-native's default modal does not allow to show multiple modals at the same time.
  • HOWEVER, if you mount multiple modals, the first one will be visible and the others will become visible, once the first one has been dismissed. So it's guaranteed that the user will see all modals, altough not at the same time, but sequentially
  • in react-native-modal, this is not the case: The others will simple be ignored, once the first one will be dismissed. This is a problem, because you will end up having multiple Modals mounted with visible=true that are actually not visible. This is inconsistent.
  • onModalHide is not an option. Imaging having multiple containers that might show a modal, depending on some state. They don't know about each other. You would need to have a global modal-managment, and only have one root Modal. This is possible, but for simpler apps just too much hazzle.

@mmazzarolo
Copy link
Member

Re-opening, sorry if I misunderstood it.

HOWEVER, if you mount multiple modals, the first one will be visible and the others will become visible, once the first one has been dismissed. So it's guaranteed that the user will see all modals, altough not at the same time, but sequentially

Oh! I'm was not aware of this -- and I don't know if I understood it correctly, could you show me a snippet? Are those modals animated?
react-native-modal is using the original modal under the hood so I don't see why they should behave differently here 🤔

onModalHide is not an option. Imaging having multiple containers that might show a modal, depending on some state. They don't know about each other. You would need to have a global modal-managment, and only have one root Modal. This is possible, but for simpler apps just too much hazzle.

Well, this is actually is a known issue of react-native about modals, alerts and dialogs.
There are multiple issues about this exact behaviour on both iOS and Android.
Here is the first one that comes to my mind.

@mmazzarolo
Copy link
Member

Closing, feel free to write here again if you want to discuss it further and I'll re-open it 👍

@kalwaniyash
Copy link

@macrozone Hey, i was facing a similar issue and i wrote a wrapper on top of the react-native-modal that does exactly what you said. If one modal is already visible and another one needs to show itself, then the wrapper will close the first modal and show the second one automatically. The UI won't freeze and stop working.
Let me know if you still need it.

@macrozone
Copy link
Author

hi guys,

I switched back to the native modal for the time beeing, but i might revisit this one here in the future.

@kalwaniyash if you have a link to a repo or gist, do you mind sharing it? thank you!

@kalwaniyash
Copy link

@macrozone the code currently is deeply integrated with my project i will soon create a public repo and share it over here.

@mmazzarolo mmazzarolo reopened this Aug 23, 2018
@mmazzarolo
Copy link
Member

Re-opening 👍

@jojonarte
Copy link

@kalwaniyash what approach did you use.

@IliRusli
Copy link

IliRusli commented Sep 7, 2018

@kalwaniyash I'm interested as well, can you tell us more?

@kalwaniyash
Copy link

@jojonarte @IlifilzaRusli Hey, the approach that i used is this.
There is a class ( say A ) that is build on top of the react-native-modal, every time some class ( say B ) needs a modal to be shown it calls class A. Class A puts the instance of class B into a queue. Now if another class ( say c ) needs a modal, it again calls A. A then checks if there is any other instance that is open. It sees that B is already open, it then closes the modal B deletes its instance and opens C and saves its instance. This cycle keeps on going.

Sorry for the delay, by this weekend i'll be making the entire code public.

@mmazzarolo
Copy link
Member

Multiple modals might work using the portal branch.
Anyone willing to give it a try?

@odenlerma
Copy link

mine worked by using onModalHide then setTimeout on the setState of the next modal

@kalwaniyash
Copy link

@jojonarte @IlifilzaRusli
Check out this library @kalwani/react-native-modal
This will do the trick for you. It has been tested in production as well. Let me know if it works out for you guys or if you are facing any issues with it.

@hpcarlos
Copy link

hpcarlos commented Dec 18, 2018

@kalwaniyash Are you going to send this proposal to the main repository?
it is working normally for me.
I have not tested on production yet, but I should test in a few days.

@scottmas
Copy link
Contributor

scottmas commented Jan 1, 2019

@kalwaniyash This solution is gold 👍. I've been fighting with this for ages now. Looking at your github repo though (https://github.com/kalwaniyash/react-native-modal-wrapper), this will be non-trivial to merge into react-native-modal core. It's also likely going to require some refactoring since you're using unsafe methods like componentWilllReceiveProps

@mmazzarolo
Copy link
Member

Closing, you can check the "Multiple Modals" section in the README.md 👍

@plaa
Copy link

plaa commented May 5, 2020

As another workaround, I created a MutexModal component (code below) that uses Redux state to only allow a single modal to be displayed at a time. The modals race to lock the mutex and the first one to acquire the lock (in practice the first one rendered) wins.

Usage: <MutexModal name="myModalName" {...otherModalProps}>...</MutexModal>

The name is used as the lock, and if not provided it works as a regular Modal. (This probably could be skipped, but it makes it easier to debug the Redux store.)

It would be great if this kind of functionality could be implemented in the react-native-modal library, because then multiple modals would just work instead of leaving synchronization up to the dev. I'm not sure though what to use as a context-agnostic replacement for the Redux state.

MutexModal.tsx
import React, { useEffect } from 'react';
import Modal, { ModalProps } from 'react-native-modal';
import { commonConstants } from '../styles/layout';
import { RootReducerState } from '../../store/types';
import {
  lockVisibleModal as lockVisibleModalAction,
  releaseVisibleModal as releaseVisibleModalAction,
} from '../../store/ui/UiActions';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';

interface OwnProps extends ModalProps {
  name?: string;
}

interface StateProps {
  visibleModal: string | null;
}

interface DispatchProps {
  lockVisibleModal: (name: string) => void;
  releaseVisibleModal: (name: string) => void;
}

type Props = OwnProps & StateProps & DispatchProps;

/**
 * A wrapper for Modal which displays at most one modal at a time. If multiple
 * modals are displayed at a time, the latter modals never appear.
 * https://github.com/react-native-community/react-native-modal/issues/171
 *
 * If the 'name' prop is not passed, it works just as Modal.
 */
const MutexModal = ({
  isVisible,
  name,
  visibleModal,
  lockVisibleModal,
  releaseVisibleModal,
  ...props
}: Props) => {
  useEffect(() => {
    if (name) {
      if (isVisible && !visibleModal) {
        // Grab modal mutex when available
        lockVisibleModal(name);
      }
      if (!isVisible && visibleModal === name) {
        // Wait for modal hide animation before releasing mutex
        setTimeout(() => releaseVisibleModal(name), commonConstants.delayHideModal);
      }
    }
  }, [isVisible, visibleModal, name, lockVisibleModal, releaseVisibleModal]);

  const visible = name ? isVisible && visibleModal === name : isVisible;
  return <Modal isVisible={visible} {...props} />;
};

const mapStateToProps = (state: RootReducerState): StateProps => {
  const visibleModal = state.ui.visibleModal;
  return {
    visibleModal,
  };
};

const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
  lockVisibleModal: (modal: string) => dispatch(lockVisibleModalAction(modal)),
  releaseVisibleModal: (modal: string) => dispatch(releaseVisibleModalAction(modal)),
});

export default connect(mapStateToProps, mapDispatchToProps)(MutexModal);
UiReducer.ts
    case ActionTypes.LOCK_VISIBLE_MODAL: {
      return {
        ...state,
        visibleModal: state.visibleModal ?? action.payload.modal,
      };
    }
    case ActionTypes.RELEASE_VISIBLE_MODAL: {
      return {
        ...state,
        visibleModal: state.visibleModal === action.payload.modal ? null : state.visibleModal,
      };
    }

@JPSantistebanQ
Copy link

I fixed this with conditional rendering:

{
showMetodo ? <ModalMetodoPago
showMetodo={showMetodo}
hiddenModal={() => setShowMetodo(false)}
onSelect={() => {
setShowMetodo(false)
setShowMetodoDetalle(true)
}}
/> : null
}
{
showMetodoDetalle ? <ModalMetodoPagoDetalle
showMetodo={showMetodoDetalle}
hiddenModal={() => {
console.warn('Hide modal')
setShowMetodoDetalle(false)
setShowMetodo(true)

				}}
				onSelect={() => {
					setShowMetodoDetalle(false)
				}}
			/> : null
		}

@452MJ
Copy link

452MJ commented May 14, 2021

Just try embed ModalB in ModalA. It will work.

<ModalA>
   <ModalB/>
</ModalA>

@Saqib92
Copy link

Saqib92 commented Jun 23, 2021

Flickering happens if using react-native picker inside modal.
https://github.com/n4kz/react-native-material-dropdown

@vishwa1937
Copy link

Just try embed ModalB in ModalA. It will work.

<ModalA>
   <ModalB/>
</ModalA>

This worked. Thanks, @452MJ .

@LESANF
Copy link

LESANF commented Feb 22, 2024

Just try embed ModalB in ModalA. It will work.

<ModalA>
   <ModalB/>
</ModalA>

absolutely thanks

@hotaryuzaki
Copy link

i have a similar issue, 1st modal i set to false then 2nd modal i set to true sequentially.
the issue is the 2nd modal not showing and the screen cannot response to any touch.
my solution is call the second modal after animation is done like this

setModal1(false);
InteractionManager.runAfterInteractions(async () => {
     setModal2(true);
});

@rabbitmouse
Copy link

i have a similar issue, 1st modal i set to false then 2nd modal i set to true sequentially. the issue is the 2nd modal not showing and the screen cannot response to any touch. my solution is call the second modal after animation is done like this

setModal1(false);
InteractionManager.runAfterInteractions(async () => {
     setModal2(true);
});

You can use setTimeout instead of runAfterInteractions, it works but is not a perfect solution

I also want to know what is the best way to switch between two different modals.

@hotaryuzaki
Copy link

i have a similar issue, 1st modal i set to false then 2nd modal i set to true sequentially. the issue is the 2nd modal not showing and the screen cannot response to any touch. my solution is call the second modal after animation is done like this

setModal1(false);
InteractionManager.runAfterInteractions(async () => {
     setModal2(true);
});

You can use setTimeout instead of runAfterInteractions, it works but is not a perfect solution

I also want to know what is the best way to switch between two different modals.

No, in most cases, the best way is to use InteractionManager.
Yes, you can change it with setTimeout, but you have to try a few times to find the perfect timing to make sure the issue won't occur anymore.
The problem is that, is hard to find the perfect timing because each phone will have a different time consumed to animate. For Android you will have a wide of range difference

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests