iOS: UI will be blocked when show Alert while closing Modal
nihgwu opened this issue ยท 184 comments
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;
+1
InteractionManager.runAfterInteractions
also doesn't work in this case.
This is really frustrating.
The opposite is also true, showing an alert in the same loop just before showing a modal will prevent the modal from being displayed
Yes, I guess they are the some issue, so I just make it simple to demonstrate
? 2016?10?20??20:07?Morgan Laupies <notifications@github.commailto:notifications@github.com> ???
The opposite is also true, showing an alert in the same loop just before showing a modal will prevent the modal from being displayed
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHubhttps://github.com//issues/10471#issuecomment-255087481, or mute the threadhttps://github.com/notifications/unsubscribe-auth/ACeY8qW8mq4UpMUPScI2sI6Uhu6dFTOlks5q11mNgaJpZM4Kbxlp.
I noticed same issue with experimental navigator. An Error while transitioning kills the animation and freezes UI.
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.
@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?
do someone have a fix for it?
@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.
Any update/resolution to this? We're still having problems with this a month after this issue was first reported.
+1
I can confirm this using https://github.com/jaysoo/react-native-prompt
any workaround?
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 } }
])
@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})} ])
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?
cc @javache
+1
+1
+1, anyone has found solutions?
+1 Same issue here!
If I run this.setState
to close the modal, and after that I run a Alert.alert
, the Modal will not be closed.
Hope someone can follow up this issue. Thanks!
Can somebody help find the commit that broke this? Usually I do that by bisecting and by looking at the history of relevant files. Once we find the cause of the regression, we can fix it and/or assign the bug to whoever authored that commit.
@ericvicenti I think this regrression was introduced by d8b2bab so @mmmulani should be the potential assignee as he made all the changes on Modal
in RN0.34
@susan-github Thank you for your answer, the as my guess. ping @ericvicenti
The solution to control the sequence by iOS code instead of setTimeout by js will be more pleasure.
ping @ericvicenti
ping @ericvicenti
+1
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 same issue...
@mmmulani Any success sorting this problem out?
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
This is still broken in RN v0.40.0
please fix ๐
For anyone interested, a simple fix is to wrap with a setTimeout
.
// MODAL STUFF HERE
setTimeout(() => {
Alert.alert('Oops!', err.message);
}, 100);
Your guys can try setting the Modal withanimationType="none"
, so the Modal can disappear immediately.
FYI, this is still broken in RN0.41. I guess the best "official" solution for this is to implement an onHidden
. I tried this suggestion but no luck.
My solution for now is that I'm combining setTimeout
and @feyy's animationType="none"
. I'm currently implementing my own custom ActionSheet so I need to show another modal "immediately" after clicking an item:
constructor(props) {
super(props);
this.state = {
visible: false,
animation: 'fade'
};
}
handleModalOnShow = () => {
this.setState({
animation: 'none'
});
}
// call this.hide(...) somewhere
hide(callback) {
this.setState({
visible: false
});
setTimeout(() => {
if (callback) callback();
this.setState({
animation: 'fade'
});
}, 10);
}
render() {
return (
<Modal
animationType={this.state.animation}
transparent
visible={this.state.visible}
onShow={this.handleModalOnShow}
>
...
</Modal>
);
}
This is the closest thing I can get. Hope this helps someone until this issue is fixed :)
EDIT: changed the code to be more "safe" i.e. setting animation back to "fade" after running the callback
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.
I had the same issue.
I covered my modal with view and in this view added onstartshouldsetreponder to call closeModal callback function which makes visibility state of modal false. Method was called modal was closed but UI was blocked too until clicking screen one time.
I changed my wrapper view with a TouchableOpacity and called the same method. Modal was closed and UI was not blocked.
Both have the same scenario I used view + onstartshouldsetresponder in first one, touchableopacity and onPress in second one.
(Note I have a few buttons in my modal I used another wrapper view to wrap them and added onStartShouoldSetResponder={(evt) => { return true}}) to make touchables that are inside of view active.
react-native 0.41.0
Try this:
new Promise((r) => {
setTimeout(r, 1000);
}).then(() => {
this.action();
});
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.
ionic instead of RN... really? (facepalm)
I solved my problem with a timeout, but it didn't work at first. One thing I noticed was I was closing my modal without dismissing the keyboard first. This is what caused the conflict. So I had to dismiss the keyboard and then dismiss the modal after a timeout.
What interesting is it was smart enough to dismiss the keyboard on its own when the modal was dismissed, but because the modal dismissal was in close proximity to dismissing the keyboard, a timeout did not work until I did it myself and put the delay in-between.
It seems like what's happening is the keyboard dismissal causes the modal animation to get canceled and revert, even though on screen it appears to dismiss just fine.
The timeout work around is not perfect, but it works. Ideally the modal shouldn't reappear like it does now.
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 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));
@chriteixeira try
this.setState({ loadingModalVisible: false });
setTimeout(() => alert(msg), 100);
Also don't forget to set animation to none
<Modal animationType="none" ... >
I have done everything mentioned above. In some places it works. In others it doesn't. Right now I am trying to dismiss a modal and show an alert - The modal won't disappear and the alert show for a brief moment and disappears. I add a timeout on top of the setState callback and the modal disappears but my alert doesn't show up.
Guys, just a suggestion for anyone looking for a quick fix.
Until this issue is fixed, it's better to do the following:
- use regular screen in the place of a modal
- hide the nav bar for this screen
- use transition bottom-to-up or fade-id
I'm suggesting this because this issue started occuring starting form some version of RN, before that it was totally fine. I did try sevaral work around as well, but it's not consistent or felt a bit hacky.
Immediately presenting an alert while the modal is dismissing will be hard to synchronize, since this is a race between the different threads React Native is operating on. I can add something to mitigate the other scenarios though. You should then be able to use requestAnimationFrame
to present it correctly.
setTimeout method works for me. I am using three modals in sequence
The following does not work (Using react-native-spinner) for modal render. Hangs the UI 80% of the time.
catch({error, status}) {
this.setState({error: error});
console.log("error " + error);
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')},
]
))
}
RENDER FUNCTION
return (this.state.isLoading === true ?
(<View style={{ flex: 1 }}>
<Spinner visible={this.state.isLoading} textContent={this.state.loadingMessage} textStyle={{color: '#FFF'}} />
</View>) :
( <View style={styles.container}>
<View style={styles.inputContainer}>
<TextInput
placeholder="USER ID"
placeholderTextColor='rgb(200,200,200)'
style={styles.input}
onSubmitEditing={() => this.passwordInput.focus()}
autoCapitalize="none"
autoCorrect={false}
returnKeyType='next'
onChangeText={ (text) => this.setState({userId: text})}
/>
<TextInput
placeholder="PIN"
keyboardType='numeric'
placeholderTextColor='rgb(200,200,200)'
secureTextEntry
style={styles.input}
returnKeyType='go'
ref={(input) => this.passwordInput = input}
onChangeText={ (text) => this.setState({pin: text})}
/>
</View>
<TouchableOpacity onPress={this.onLoginPressed.bind(this)}>
<View style={styles.buttonContainer}>
<Image
source={require('../../../data/images/LoginIcon.png')}
style={styles.buttonIcon}/>
<Text style={styles.buttonText}>LOGIN</Text>
</View>
</TouchableOpacity>
</View>
)
);
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")},
])
})
})
I have the same issue
+1
Any solutions for that?
+1
+1
I am not facing the problem with Alert, but closing one Modal and opening other.
Using timeout solved the problem for now.
+1
It does seem to work if I set the animationType to 'none' but that is just not a viable solution for me.
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.
@davidlongest I tried react-native-modal
, but I'm experiencing this bug with that modul as well. See this Expo snack with a modified version of the react-native-modal
example with alert on onModalHide
: https://snack.expo.io/HkaZV7f_W
solution is worked for me is:
Alert.alert('Error', 'Please check your internet connection and try again', [ { text: "OK", onPress: () => { this.setState({loading: false}); }} ], { cancelable: false });
I have the same issue when I programmatically close a Modal that is a Child of another Modal.
The Animations of both just collide and the app freezes, needing a restart.
Same issue in RN v0.48.2 with "react-native-router-flux": "^4.0.0-beta.18"
The issue is caused by 'redux' action. I solved this problem by putting redux action in setTimeout method.
+1
+1
Same issue with RN v0.48
+1
+1
putting redux action in setTimeout doesn't work for me, and neither does setting the animation to 'none'
i've noticed this message in the xcode logger: Warning: Attempt to dismiss from view controller <UIViewController: 0x101b0b7d0> while a presentation or dismiss is in progress!
also, I'm not trying to show an alert. I'm simply trying to dismiss a modal, and it locks up on me. The only way to get rid of the modal is to force quit the app (on a device), or quit and restart the simulator.
same problem here. wrapping a component with an onHide callback based on props is not the way to go. The React Native team should build it into the native code properly such that if we want to show successive modals, there should be no need for setTimeout or workarounds described here.
+1
react-native: 0.47.2
+1
+1
+1
react-native-cli: 2.0.1
react-native: 0.48.4
If the alerts are not called from the modal (like by a background process or listener), timeouts won't work, as the timing is unpredictable. I look forward to a permanent solution, but perhaps for now a javascript-based modal could be used in some cases (like this one: https://github.com/bodyflex/react-native-simple-modal).
It seems that commit a389ffb 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>
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(...)
});
});
FYI: there's setImmediate
but it's still an ugly workaround.
RN 0.50 is now released and the onDismiss
prop is available as a possible fix for this issue.
This does not resolve when you open multiple modals (one inside another), and hide all of them at the same time.
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!
+1
+1
+1
Still happening in react-native: 0.51.0
@mikaello wonโt help so much because Alert Component triggers it too and is missing this feature
Does this issue be solved?
using onDismiss should fix this particular problem. if you have multiple Modals, ensure that they're all dismissed before creating an Alert.
I'm closing this because someone has confirmed that using onDismiss fixed it for them. If you have some insane other navigation hierarchy where you're still seeing this bug, please comment with an example.
Thansk @nightflash It perhaps worth mentioning the required import:
import { InteractionManager } from 'react-native';
@mmmulani it is not fixing this - using stock alert alone with an Navigation action is enough to trigger this issue and alert is missing an equivalent ondismiss property working the same as an modal.
hey, yeah this issue concerns solely the combination of Modal and Alert.
could you make a new issue with the Navigation issue combined with Alert problem?
feel free to propose some solutions or put up a pull request with onDismiss for Alert. This area is pretty unmaintained because we aren't using it but I would gladly review a pull request, or give advice on getting it working
@mmmulani onDismiss
is not a viable option, because we don't actually want to show an alert message on modal dismiss.
For example we might have 3 distinct actions:
action1 = () => {
alert("action1performed");
this.closeModal()
};
action2 = () => {
alert("action2performed");
this.closeModal()
};
action3 = () => {
alert("action3performed");
this.props.navigation.goBack();
};
action4 = () => {
alert("action4performed");
};
As you can understand, we may have multiple actions, which should produce different alert messages. If I were to put the alert on dismiss, this would mean that first the alert will be done after, and then I won't be able to know which actions did trigger the dismiss. For sure I can store the action somewhere and then use the stored value on the dismiss callback but this means really bad spaghetti code in perspective.
Plus, the fact that the modal will unmount can be a side-effect of an action being performed, like action3 that does not explicitly say the modal should close but simply do a backward navigation (which will unmount the modal). (probably the case mentioned by @K-Leon)
Obviously, the example above is a simplified version, and the different actions triggering alert and backward navigations may be done at different levels in the component hierarchy. I don't expect putting an alert that is not even close to the modal component view to ever trigger an app freeze.
@mmmulani I don't understand why this issue is closed at all. onDismiss is at most a workaround. And for advanced usecases it's a really bad one. There's still a bug to resolve on this. It's not normal to have different behaviors on iOS and Android regarding alerts, and not normal that intuitive code usage does lead to a totally unusable app.
Not saying it's the fault of anyone, not saying RN contributors are bad guys, and I know I can contribute and fix the bug myself. I am just saying this issue should not be closed.
could you make a new issue with the Navigation issue combined with Alert problem?
@mmmulani I don't see the point, it's still the same issue, because the navigation action does trigger modal unmounting so alerting and performing navigation action is like alerting and removing modal.
The workaround of @nightflash does not even work for me, using this instead of normal alert crash my app now:
export const safeAlert = (...args) => {
InteractionManager.runAfterInteractions(() => {
setTimeout(() => {
alert(...args);
},0);
});
};
I can't use onDismiss
easily in my app, because all modal dismiss should not be handled the same way, and are handled at different levels in component hierarchy.
The only viable workaround for this issue so far is to not display any alert and replace them with an absolute view layer (like toasts or something), as mentioned by @wmonecke
ahhh I see, yea the only use case we've seen is someone showing an alert after dismissing the modal. Didn't realise people were doing them both at the same time.
In that case, feel free to submit a PR to fix this, whether the issue is open or not really doesn't matter