react-native: iOS: UI will be blocked when show Alert while closing Modal

When show Alert while closing Modal, the Alert dialog will disappear and the Modal will block the UI entirely even after reload, only on iOS.

'use strict';

import React, { Component } from 'react';

import {
  StyleSheet,
  View,
  Text,
  Modal,
  Alert,
} from 'react-native';

class demo extends Component {
  state = {
    showModal: false,
  }

  onShowModal = () => {
    this.setState({ showModal: true });
  }
  onCloseModal1 = () => {
    this.setState({ showModal: false }, () => {
      Alert.alert('Alert', 'UI will be blocked by the modal');
    });
  }
  onCloseModal2 = () => {
    this.setState({ showModal: false }, () => {
      setTimeout(() => {
        Alert.alert('Alert', 'Alert won\'t show');
      }, 200);
    });
  }
  onCloseModal3 = () => {
    this.setState({ showModal: false }, () => {
      setTimeout(() => {
        Alert.alert('Alert', 'Works fine');
      }, 510);
    });
  }
  render() {
    const { showModal } = this.state;
    return (
      <View style={styles.container}>
        <Text onPress={this.onShowModal}>Show modal</Text>
        <Modal animationType='slide' visible={showModal} onRequestClose={this.onCloseModal3} >
          <View style={styles.container}>
            <Text onPress={this.onCloseModal1}>Close modal immediately</Text>
            <Text onPress={this.onCloseModal2}>Close modal after 200ms</Text>
            <Text onPress={this.onCloseModal3}>Close modal after more then 500ms</Text>
          </View>
        </Modal>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'space-around',
  },
});


export default demo;

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 269
  • Comments: 184 (46 by maintainers)

Commits related to this issue

Most upvoted comments

today I met the similar problem after I upgraded the react-native from 0.33 to 0.37. I want to show an Alert dialog after close the Modal, but Modal doesn’t disappear, even after I close the Alert dialog and use cmd + R to reload the app. only in iOS, and it works fine by react-native 0.33.

the code likes following:

  renderModal() {
    return (
      <Modal
        animationType = 'fade'
        transparent={true}
        visible={this.state.isProcessing}
        onRequestClose={()=>{}}>
        <View style={styles.modalContainer}>
          <LoadingSpiner size='large' color='white' styleAttr='Normal'/>
        </View>
      </Modal>
    )
  }

  _pressNext() {
    // display a Modal with a spinner
    this.setState({isProcessing: true}}

    // network request
    // ...
  }

  componentWillReceiveProps(nextProps) {
      // ...

      // to hide the Modal with a spinner
      this.setState({isProcessing: false})
      Alert.alert('title', 'Something has done!', [
        { text: 'Got it', onPress: () => {} }
      ])
    }
  }

then I try to use setTimeout to work around it, the code likes following:

  componentWillReceiveProps(nextProps) {
      // ...

      // to hide the Modal with a spinner
      this.setState({isProcessing: false})
      setTimeout( () => {
        // this log will output
        console.log("show alert")
        // but Alert doesn't display
        // sometimes it will display occasionally
        Alert.alert("title", "msg")   
      }, 200)
  }

then the Modal will disappear, but, the Alert dialog can’t display!!!

I also tried run setTimeout in setState callback, like this:

  this.setState({isProcessing: false}, () => {
    setTimeout( () => {
      Alert.alert("title", "msg")
    }, 200)
  }

but the same result, Alert dialog doesn’t pop up yet.

finally, I decide to hide Modal after I close the Alert dialog, and that works! code likes following:

Alert.alert("title", "msg", [
  { text: "OK", onPress: () => { this.setState({ isProcessing: false } }    
])

+1

Any update/resolution to this? We’re still having problems with this a month after this issue was first reported.

Using setTimeout is NOT a safe way to avoid this problem. I recommend using onDismiss props in iOS to do something after the modal closed (including Alert some messages). I created a class named ModalView to replace RN Modal class.

class ModalView extends React.Component {

    state = {
        visible: false
    };

    close({then} = {}) {
        if (Platform.OS === 'ios') {
            this.setState({visible: false, onDismiss: then});
        } else {
            this.setState({visible: false});
            if (then !== undefined) {
                then();
            }
        }
    }

    show() {
        this.setState({visible: true});
    }

    render() {
        return (
            <Modal
                visible={this.state.visible}
                onDismiss={this.state.onDismiss}
                {...this.props}>
                {this.props.children}
            </Modal>
        )
    }
}

Using

Just replace your Modal with ModalView and add ref props.

<ModalView ref={ref=>this.modalView=ref} 
   //other Modal props
>
//your components
<ModalView>

Show Modal

this.modalView.show();

Close Modal

this.modalView.close();

Alert some messages after the modal closed.

this.modalView.close({
      then: () => {
          Alert.alert('Hello World');
          //your code
      }
});

hey, current maintainer of Modal here. The Modal-Alert combination (or really any followup interaction from Modal closing) has been pretty tough to get working right.

I’ll try to spend some time on it this Friday, I think the proper solution would be to add some kind of onClose handler that let’s JS do a follow up action when we can be certain that native has navigated away already.

The best workaround is to wait for the end of the animation and then run Alert in the next “tick” using setTimeout without the second argument. You will be independent of the animation duration:

InteractionManager.runAfterInteractions(() => {
    setTimeout(() => {
        Alert.alert(...)
    });
});

For everyone who suggest to use timers: Maybe this bullshit codding is ok for JS or Android… but for native iOS developer it looks 100% unacceptable. Better to disable modal animation at all…

@Jlexyc Take it easy. No one is forcing you to use RN. Go with Ionic, or w/e, if you don’t like it or you are not satisfied by the development work being done.

still have it in rn 0.59.8

I think use setTimeout() is the best solution right now.

setTimeout(() => {
      this.onReset();
      Alert.alert("Confirm failed !", "The wrong answer.");
}, 300);

@yogesh1490

I have a similar problem, but the alert may appear at an undetermined point in time and is not logically dependent on Modal component.

My workaround looks like this:

  const OldAlert = Alert.alert;

  Alert.alert = (...args) => {
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        OldAlert(...args);
      });
    });
  };

This is “magic,” but it works for me.

Modal isn’t really being actively maintained, and is going to be moved out of the React Native core in the future as the “slimmening” commences. I’d recommend migrating off of it in favor of something like React Navigation’s StackNavigator. Example:

const RootNavigator = createStackNavigator(
  {
    App,
    ThreadPickerModal,
    AddUsersModal,
    CustomServerModal,
    ColorPickerModal,
    ComposeSubthreadModal,
  },
  {
    headerMode: 'none',
    mode: 'modal',
    transparentCard: true,
  },
);

How is this still an issue after a YEAR? Updating RN 0.47 to 0.48.3 re-introduced this error AGAIN!

Upgrading to RN 0.50.x brings too many breaking changes that make it worth it.

This makes RN so frustrating to work with. Wasting two whole days just to deal with this, to end up creating a position: absolute View on top. Christ!

SIMPLE WORKAROUND DISCOVERED

Note: setTimeOut does not work for me.

Setup recursive requestAnimationFrame to force the call to be done during seperate frames.

Also, you’re right @susan-github, the presentViewController is done during the wrong time. Also the dismissViewController needs to be called from the parentViewController. For some reason, the ModalView maintains the modal view controller between app states. @ericvicenti there could be something to do with the way the modal is presented then dismissed. At least now we have an easy work around.

In iOS/Swift I have had strange results with having a ModalView be tied to the window, it needs to be tied to the parent view controller.

WORK AROUND

catch({error, status}) {
            this.setState({error: error});
            console.log("error " + error);
            requestAnimationFrame(() => {
                this.setState({isLoading: false})
                requestAnimationFrame(() => {
                    Alert.alert(
                        'ERROR',
                        'Unable to connect to the server, please check your username and password.',
                        [
                            {text: 'OK', onPress: () => console.log("Error")},
                        ])
                })
            })

Your guys can try setting the Modal withanimationType="none", so the Modal can disappear immediately.

Same here. any update on this?

For anyone interested, a simple fix is to wrap with a setTimeout.

// MODAL STUFF HERE
setTimeout(() => {
  Alert.alert('Oops!', err.message);
}, 100);

This is still broken in RN v0.40.0

Hi everyone, I solved this problem by adding 100ms timeout as recommended in the comments above. I guess we need to set timeout and waiting for the modal close first

    setTimeout(() => {
      Alert.alert(name,  message);
    }, 100);

Hi guys. Have the same issue right now. Are there any plans to resolve this in near future?

UPD: Solved this by adding 500ms timeout as in the comments above

After asked iOS developer, it is caused by presentViewController. presentView will be show after the previous one closed completely. While after compare 0.38 with 0.33 version, the Modal component start using presentViewController, which then lead to conflict with Alert. dingtalk20161213151617

@baurine Had the same problem with RN 35, a little higher timeout worked for me (600), but ultimately i also did what you did, hide the modal on OK Pressed in Alert

return Alert.alert('Σφάλμα','Η υπηρεσία δεν είναι διαθέσιμη αυτή τη στιγμή. Παρακαλούμε δοκιμάστε σε λίγο.', [ {text: 'OK', onPress: () =>this.setState ({spinnerIsVisible:false})} ])

Still an issue with RN 0.61.2 - does anyone if this is fixed in any later 0.61 patch releases?

Yes, @cristianoccazinsp I think this is the only possible solution as of now because the other availed solution didn’t help me in any way. As from the last 2 days I was stuck in this issue, I started giving a try to the “AlertIOS” component and fortunately found it successful.

Yes, Alerts uses AlertIOS behind it, but don’t know how it got successful? Maybe something lacks in implementing of AlertIOS in Alert behind the scenes.

To be more specific to the code for both platforms;

(Platform.OS === 'android' ? Alert : AlertIOS).alert(
  'Alert Title', 'Alert message',
    [
      {
        text: 'OK', onPress: () => this.setState({ loading: false})
      }
    ]
);

I’m also having this issue on RN 0.57.8

@DevBkIL my not great solution is to set a timeout, enough for the presented view controller to be the one under the Modal, and then present the Alert. Like I said, not great, but it’s working until this can be properly resolved.

I noticed same issue with experimental navigator. An Error while transitioning kills the animation and freezes UI.

I think facebook engineering need fix this bug.

I’ve build my own modal with position='absolute' and it works perfectly without additional delays for Alert. Also you can build custom appearance animation for it. So just don’t use modal out of the box.

As @baurine did good investigation.

Modal need 500ms to dismiss, so it’s safe to show the Alert after 500ms.

Until someone come with good solution below hack is work for me. The issue is conflict between Model and Alert. As he said Model need 500ms to dismiss before you show alert.

//I guess the Modal need 500ms to dismiss, so it's safe to show the Alert after 500ms.
if (!auth.isRegistered && newProps.auth.isRegistered) {
      setTimeout(() => {
        this.registrationDoneAlert();
      }, 500); 
}

@slorber Fair enough, but this should be a design decision, not forced because the underlying tech is broken.

Come on, please adress this issue

ionic instead of RN… really? (facepalm)

It’s not a surprise that it is OK for JS, it’s a timeout, it’s Javascript, it’s async bridge. There are a lot of concepts that make this workaround a very natural solution.

On Wed, 1 Mar 2017 at 13:56 Oleksii notifications@github.com wrote:

For everyone who suggest to use timers: Maybe this bullshit codding is ok for JS or Android… but for native iOS developer it looks 100% unacceptable. Better to disable modal animation at all…

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/facebook/react-native/issues/10471#issuecomment-283332541, or mute the thread https://github.com/notifications/unsubscribe-auth/ACWcxlcKWv9Zaqalf2a_lSz-c9H1r9NUks5rhWsIgaJpZM4Kbxlp .

I can confirm this using https://github.com/jaysoo/react-native-prompt

any workaround?

@ericvicenti I didn’t use NE or Navigator in demo at all, and I don’t think this issue is related to InteractionManager, my guess is that Modal and Alert are using the same controller or view, so there will be a conflict when trying to control them simultaneously, I know nothing about iOS or I would be glad to make a PR, because it’s really annoying but has a quite common use case

In my demo, I’ve show the different results when waiting for a certain milliseconds. I guess the Modal need 500ms to dismiss, so it’s safe to show the Alert after 500ms. Perhaps you are right to tie Modal animations into InteractionManager, but what if I show the Alert first as @ganmor mentioned above, I can’t close the Alert manually, or if I could, would there be the same occasion that we need to wait for the InteractionManager before showing the Modal?

Would someone expert in iOS take a look at this issue?

@cristianoccazinsp The first thing I used was InteractionManager.runAfterInteractions, but for some reason, this did not work for me, so I had to make 2 requestAnimationFrame to defer the call to Alert.alert to the next iteration in the event loop.

To add to the above, it really depends on the device you are using. For lower-end devices that run slower, you need a higher timeout. My usual timeout is 300ms.

Given this is such a major issue and such a common use-case I’m still surprised it has persisted for 2 years. Any updates @ericvicenti ?

I just published my first RN package after few weeks of learning/using this framework – and yes its far more better than other cross platform out there.

The package includes a custom ActionSheet that uses the modal which works well for me (disabled the default animation and created my own). If anyone interested: https://github.com/lodev09/react-native-cell-components

I’m not sure if this is the same issue, but if I hide the modal while dismissing the keyboard the modal re-appears and the contents can’t be interacted with.

The opposite is also true, showing an alert in the same loop just before showing a modal will prevent the modal from being displayed

I have same issue here when i use “react-native”: “0.59.9” and “react-native-modal”: “^11.3.1”

And my solution is show the alert in Modal onDismiss props

Hi all, I’ll be closing this because we may fixes it in master branch, feel free to reopen if you think bug not fixed.

Modal isn’t really being actively maintained, and is going to be moved out of the React Native core in the future as the “slimmening” commences. I’d recommend migrating off of it in favor of something like React Navigation’s StackNavigator. Example:

const RootNavigator = createStackNavigator(
  {
    App,
    ThreadPickerModal,
    AddUsersModal,
    CustomServerModal,
    ColorPickerModal,
    ComposeSubthreadModal,
  },
  {
    headerMode: 'none',
    mode: 'modal',
    transparentCard: true,
  },
);

In some cases, it is really useful to have the Modal component available. I do not consider the stack navigator with modal transition as a complete replacement for it.

I work with React Navigation Stack Navigator, however, we need some alternative flows for tablets for our custom flow (which is similar to iOS SplitView design to make advantage of tablets size), in those flows, Modal becomes handy when we have to display something in top of the rest of the whole UI.

An example: Left pane: Stack Navigator (1), Right pane: Stack Navigator (2) … In top of it: Modal

For this scenario, if you use Stack Navigator modal transition, you cannot display something in the whole screen. You can however, display that in the left of right pane.

We are having a similar issue in niftylettuce/react-native-loading-spinner-overlay. Could it be that this is a more general issue relating to UI updating on state change rather than just a modal issue?

Looks like we need <Modal> animations to tie into InteractionManager. A PR would be appreciated for this!

Any issues with NavigationExperimental should be filed separately.

2 years later and it is still a thing. Can’t hide 2 modals in a single setState call because of this. You got to manually set a timeout for the second one to close.

Just an FYI - while it’s not ideal and should be fixed, I’ve had the 10ms setTimeout out in production for months now without issue.

@tiendn That’s what we use but it’s a potential race condition if for some reason the initial modal doesn’t dismiss quickly enough

It seems that commit a389ffbd84224b583e71cf7c1468409cbc91ec8e resolves this issue. It looks like it will be shipped with RN 0.50, try it now by upgrading to 0.50 RC 1:

$ react-native-git-upgrade v0.50.0-rc.1

and add this to the modal component:

<Modal
   ...
   onDismiss={() => alert('Hey, no freeze')}
>
   ...
</Modal>

+1 react-native-cli: 2.0.1 react-native: 0.48.4

@revich2 interesting… Why do you need 2 requestAnimationFrame ? Would it be the same with runAfterInteractions ?

@react-native-bot can we re open this?

You can use my solution (prevent opening Modal when Alert is opened, and open it after Alert was closed, using redux and hooks).

My solution is not full, later I will send solution that prevents opening many Modals at same time and prevents opening Alert while Modal is closing (based on queue and store subscribe).

In any case, you can refine my idea.

Modal + Alert.zip

Still having this issue. I am showing a loader on screen which is a Modal and then I need to show popup after API response. It works on Android to hide loader on Ok button action of Alert popup, but not working on iOS. Loader gets stuck on screen and popup never appears. Getting this error on Xcode : Warning: Attempt to present <RCTModalHostViewController: 0x7fda01a0d970> on <UIViewController: 0x7fd9fe447fd0> which is already presenting <RCTModalHostViewController: 0x7fda01a0d2d0> Need to kill the app and restart it. Can anyone have solution?

same question! It’s very incredible. I found, the question always appears in the real machine, but not in the simulator.

The timeouts solutions have worked for me. My problem is that when the component is updated, if I was showing an Alert.alert in iOs, the alert disappears right after the setState or Redux update. I think it must have to be with some lifecycle event issue or re-rendering the component, which makes the alert in iOs to go away. I hope this to get solved in the near future. Thank you.

I just tried this out and onDismiss seems to work for me.

The below code is probably not good practice

Alert.alert('We are dismissing the modal after this');
this.setState({modal_visible: false});

The way iOS handles modals, It’s almost the same as doing …

Alert.alert('First alert');
Alert.alert('Second alert');

When I tried the above code, iOS only shows the first alert, and Android only shows the 2nd alert.

Possible solution for dismissing modal after an Alert

Assuming you have a modal like

  <Modal visible={this.state.modal_visible}>
    ...
  </Modal>
Alert.alert(
  'Dismissing',
  'We are dismissing the modal after this', 
  [
    {text: 'OK', onPress: () => this.setState({modal_visible: false})}
  ]
);

Possible solution for showing an Alert after dismissing a modal

<Modal onDismiss={() => Alert.alert('Modal dismissed')}/>

Just in case you have a button or some other action, that triggered the visibility of your modal, and you want to know which button did that when the modal closes, feel free to use this.setState({modal_triggered_by: 'action1'}) in that action, and then use this.state.modal_triggered_by in the modals onDismiss

But having said all this, I do agree that the behaviour of Modal/Alert combination should work out of the box, and should be identical on both platforms.

@isaachinman totally agree, just giving you this as a hint to solve your problem. As you can see I advocated to keep this issue open

+1 Still a problem on iOS with RN 0.53.3 ( Android Modal works fine RN 0.53.3 )

		"react": "^16.2.0",
		"react-native": "^0.53.3",

I really DO NOT like the setTimeout work-around.

@baurine Has the best work-around.

finally, I decide to hide Modal after I close the Alert dialog, and that works! code likes following:

Alert.alert("title", "msg", [
  { text: "OK", onPress: () => { this.setState({ isProcessing: false } }    
])

Set the Modal visible isProcessing state to false when the Alert is closed or dismissed.

Instead of using setTimeout(), react-native-modal also has a onModalHide prop callback that can be used to show the alert after the modal has hidden. This can be a bit less fragile.

I’m experiencing the same problem. I have also tried to add the alert in the setState callback but the problem remains:

this.setState({ loadingModalVisible: false, }, () => alert(msg));

hey sorry, I got caught up with some other things at work. I looked into this a bit and it seems like #10303 might actually be able to fix this. Could any of you try it and let me know? If so we can work on getting that merged

UPDATE: UI will be blocked when show Share/ActionSheet while closing Modal too And if we are showing the Alert/Share/ActionSheet, then show a Modal, the UI will be froze too ping @javache @mkonicek