React-Native Controlled Modal Freezing - react-native

Disclaimers:
Only can test on iPhone emulator atm.
React-Native 0.49
Mac OSX High Sierra
I want to create a modal which gets its props from a parent component.
As below:
const Modal = ({ showModal, closeModal }) => (
<Modal
animationType="slide"
transparent={false}
visible={showModal}
onRequestClose={() => {alert("Modal has been closed.")}}
>
<View style={{marginTop: 22}}>
<Text>Hello World!</Text>
<TouchableHighlight onPress={() => closeModal() }>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</Modal>
);
This is the parent example:
<View>
<Modal
showModal={this.state.showModal}
closeModal={() => this.setState({ showModal: false })}
/>
<ScrollView>
{elements.map(element => {
return (
<Card key={element.id}>
<Badge onPress={() => this.setState({ showModal: true })>
<Text>Show</Text>
</Badge>
</Card>
);
})}
</ScrollView>
</View>
When I click the show modal button the modal pops-up as expected but when I click closeModal then the modal disappears and reappears again but this time I cannot interact with it, the UI seems as if it is frozen, I have to then restart the emulator.
If I copy and paste the code straight from the React-Native docs:
https://facebook.github.io/react-native/docs/modal.html
The modal works fine. It is a self-contained component though.
Any help/advice would be much appreciated.
Regards,
Emir

After painfully rebuilding the component from scratch I see there was a unsuspected culprit:
componentWillUpdate() {
UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
LayoutAnimation.easeInEaseOut();
}
When I removed this code the modal worked fine but when I added it back it froze when I tried to close. This seems to be some animation conflict in iOS cant confirm for Android.
When I added a timeout of 1000ms the screen revealed a little more before it froze again.
So for now if someone has the same issue look for multiple animations being called.
Hope this helps someone, and if you have a better way of solving it please do let me know.
Regards,
Emir

This is a know issue and nothing to do with your code.
See here: https://github.com/facebook/react-native/issues/16895

Make sure that your <Modal/> is wrapped in a <View/>.

Very late, but issue still exist in latest version, And only solution i found is make different views in render method.
one for modal and one for other component.
render() {
if (showErrorModal) {
return (
<ModalError message={message} visible={showErrorModal} handleBack={this.handleBack} />
);
}
return (
<ScrollView style={{ flex: 1 }}>
<View style={{ padding: 10, paddingVertical: 20 }}>
{!active ? this.fieldlabel() : this.fieldSelect()}
</View>
// remove this one, do not use here. it will block the UI
{* <ModalError message={message} visible={showErrorModal} handleBack={this.handleBack} /> *}
</ScrollView>
);
}
}

Inside your Parent component, create a function will set showModal to false.
Parent
closeModal = () => {
this.setState({
showModal: false
});
}
Then you need to pass it down to your Modal, via props.
<Modal showModal={this.state.showModal} closeModal={this.closeModal} />
Inside of your Modal, change:
<TouchableHighlight onPress={() => this.closeModal() }>
To:
<TouchableHighlight onPress={closeModal}>

Why do u use this.closeModal()? Use the one u are getting from the props,i.e just closeModal().
<TouchableHighlight onPress={() => closeModal() }>

Related

React Native: Disable animations when onPress function is called inside Animated.View

I have a animated view stack which contains a Pressable component to navigate to another screen. But when I press the button, navigation happens after the animation of the container. I want to make it instant.
<Animated.View>
<Animated.View style={[styles.headerContainer, titleStyle]}>
<Animated.Text style={styles.verificationTitle}>
{activationBoxTitle}
</Animated.Text>
<ArrowRightWhite />
</Animated.View>
<Animated.View style={[styles.container, descriptionContainerStyle]}>
<ImageBackground
style={styles.backgroundImage}
resizeMode="cover"
source={images.activateAccBackground}
>
<Pressable
onPress={() => navigation.navigate(
"GeneralQuestions", {
screen: navigateActivationToPage
})}
>
<Animated.Text style={descriptionStyle}>
{activationBoxDescription}
</Animated.Text>
</Pressable>
</ImageBackground>
</Animated.View>
</Animated.View>
How can I do that?
I've seen a problem like this in a question here and I've also seen it in a project. The most common is before navigation, add a setTimeOut, this will cause a delay and have the animation and navigation. It would look something like this:
function handleNavigate() {
setTimeout(() => {
navigation.navigate('GeneralQuestions', {
screen: navigateActivationToPage,
});
}, 5000);
}
// ...
<Pressable
onPress={() => handleNavigate}
/>
Then at setTimeOut time, you add a value that makes sense according to your animation

"react-native-loading-spinner-overlay" is not working when modal is open in iOS

"react-native-loading-spinner-overlay" is not working when modal is open in iOS. If I set modal visible to false, it works.
(Android is working well in any case.)
That is my code.
When modal is open, if I click upload button on modal,
_onUpload = () => {
this.setState({ isLoading: true }) //-----> Loading spinner is not working.
}
If I make like this
_onUpload = () => {
this.setState({ modalVisible:false }) //-----> After modal turns off
setTimeout(() => {
this.setState({ isLoading: true }) //----> Loading spinner works.
}, 500);
}
render
render() {
return (
<View>
<Spinner
visible={this.state.isLoading}
textContent={'Loading...'}
textStyle={{ color: 'white' }}
/>
</View>
)
}
I used modal from 'react-native-modal'
import Modal from 'react-native-modal';
<Modal
isVisible={modalVisible}
backdropColor="#B4B3DB"
backdropOpacity={0.8}
animationIn="zoomInDown"
animationOut="zoomOutUp"
animationInTiming={1000}
animationOutTiming={1000}
backdropTransitionInTiming={1000}
backdropTransitionOutTiming={1000}>
<View style={styles.modalBody}>
<TouchableOpacity onPress={() => this._onUpload()} >
<Text>Upload</Text>
</TouchableOpacity>
</View>
</Modal>
Why it is not working on only iOS?
How to make it work on iOS when modal is open?
You cannot use one Modal on another Modal in ios,
you have to close the first Modal before opening a second Modal.
like,
setstate({isLoading:false,modalVisible:true})
or you can use ternary like,
{modalVisible?
<View style={styles.modalBody}>
<TouchableOpacity onPress={() => this._onUpload()} >
<Text>Upload</Text>
</TouchableOpacity>
</View: null}
Add a timeout to wait for the first Modal to close.
setTimeout(() => {
setstate({modalVisible:true})
}, 2000);
Creating your own spinner is the best option I come across for this issue.
{showSpinner && (
<View style={{ ...StyleSheet.absoluteFill, zIndex: 1 }}>
<ActivityIndicator color={Colors.gray900} size="large" style={{ flex: 1, backgroundColor: Colors.gray300, opacity: 0.3 }} />
</View>
)}

DoneButton is not rendering in 'react-native-app-intro-slider-rerender-on-prop-change'

I am trying to make a multiple choice test with a few questions using React Native package 'react-native-app-intro-slider'. The problem was that this package was not rerendering the items when something changes, state for example. I found a similar or refactored package called 'react-native-app-intro-slider-rerender-on-prop-change' that did the same thing but rerendered items on a state change. But the problem is that it does not render the Done button at last slide.
The last package i used was this :
import AppIntroSlider from 'react-native-app-intro-slider-rerender-on-prop-change';
The render method for the 'MCTContainer' (Multiple Choice Container) class is :
render() {
if (this.state.showRealApp) {
this.props.navigation.navigate('TopicGroupDetails', { topicGroup: this.state.topicGroup });
return null;
} else {
const questions = this.state.questions;
return (
<AppIntroSlider
renderItem={this._renderItem}
slides={questions}
onDone={this._onDone}
onSkip={this._onDone}
activeDotStyle={{ backgroundColor: '#039BE5' }}
showPrevButton
showNextButton
showSkipButton
showDoneButton
renderPrevButton={this._renderPrevButton}
renderNextButton={this._renderNextButton}
renderSkipButton={this._renderSkipButton}
renderDoneButton={this._renderDoneButton}
/>
)
}
}
and its renderDoneButton method is like following :
_renderDoneButton = () => {
return (
<View style={styles.buttonCircle}>
<Icon
name="check"
color="#039BE5"
size={24}
style={{ backgroundColor: 'transparent' }}
/>
</View>
);
};
This is actually the same, exactly the same as I used for the component 'SliderComponent' where I show tutorial introductory text in multiple slides. It renders the 'done' button fine. But the only differenence is that I used package 'react-native-app-intro-slider' there, not 'react-native-app-intro-slider-rerender-on-prop-change'.
Please help me if someone has encountered such a problem. Thanx in advance ...
I finally solved it.
I just added a condition on "renderItem" function of the "AppIntroSlider", that checks whether it is the last slide. If it is it renders a button that calls "AppIntroSlider"-s "_onDone" method that completes the slide show.
_renderItem = ({item}) => {
return(
{
(this.state.isLastSlide) ?
(
<TouchableHighlight
onPress={this._onDone}
>
<View>
<Text>
COMPLETE SLIDE SHOW
</Text>
<Icon
name="check"
color="white"
size={24}
/>
</View>
</TouchableHighlight>
) :
(<View></View>)
}
)
}

How can I use tabs in a modal in react-native

I have modal for filtering search resluts , something like foursquare app . I have filters in diffrent categories and I need to use tabs for each category . for example when user clicks each tabs it shows the filters related to that tab . and user can select checkboxes or radio buttons . and at the end when user checks all of their needed filters I need to make http request with the new filters.
Something like the image below . I created the modal but I need the functionality for tabs and at the end making the api request with the selected options:
You can also create custom tabs using <Text> with state and depending on a state value render a View associated with that tab. for example
state = {
modalVisible: false,
currentTab: 1,
};
onTabClick = (currentTab) => {
this.setState({
currentTab: currentTab,
});
};
// inside render
<Modal
animationType="slide"
transparent={true}
visible={this.state.modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}>
<View style={styles.tabs}>
<Text
onPress={() => {
this.onTabClick(1);
}}
style={[
styles.tabTextStyle,
this.state.currentTab === 1 ? styles.tabUnderline : null,
]}>
GENDER
</Text>
...
</View>
{this.state.currentTab === 1 && (
<View>
<Text>GENDER</Text>
</View>
)}
...
snack example
Modal is just a Container like View. You can draw anything inside it.
First, import {Modal} from 'react-native'
Then, in your modal, embed anything what you want:
<Modal visible={ this.state.modal }
animationType="fade" transparent={true}
onRequestClose={_ => this.setState({ modal: false }) }>
<View>
{/*
Do anything. Its an open ground.
Whatever component, styles, props and/or anything else you want, you can design
*/}
{/* For example, I am adding a close button */}
<TouchableOpacity style={{ alignSelf: 'flex-end' }} onPress={_ => this.setState({ modal: false }) }>
<Icon type="FontAwesome" name='times' style={ styles.closeIcon } />
</TouchableOpacity>
</View>
</Modal>
And you can open your modal from anywhere like:
<TouchableOpacity style={ styles.button } onPress={_ => this.setState({ modal: true }) }>
<Text style={ styles.buttonText }>Open Modal</Text>
</TouchableOpacity>
Finally, for tabs, you can use either of:
NativeBase Tab Component
React Native Tab View

React native onPress with TouchableWithoutFeedback is not working

I am developing a simple React Native application for learning purpose. I am just taking my initial step to get into the React Native world. But in this very early stage, I am having problems. I cannot get a simple touch event working. I am implementing touch event using TouchableWithoutFeedback. This is my code.
class AlbumList extends React.Component {
constructor(props)
{
super(props)
this.state = {
displayList : true
}
}
componentWillMount() {
this.props.fetchAlbums();
}
albumPressed(album)
{
console.log("Touch event triggered")
}
renderAlbumItem = ({item: album}) => {
return (
<TouchableWithoutFeedback onPress={this.albumPressed.bind(this)}>
<Card>
<CardSection>
<Text>{album.artist}</Text>
</CardSection>
<CardSection>
<Text>{album.title}</Text>
</CardSection>
</Card>
</TouchableWithoutFeedback>
)
}
render() {
let list;
if (this.state.displayList) {
list = <FlatList
data={this.props.albums}
renderItem={this.renderAlbumItem}
keyExtractor={(album) => album.title}
/>
}
return (
list
)
}
}
const mapStateToProps = state => {
return state.albumList;
}
const mapDispatchToProps = (dispatch, ownProps) => {
return bindActionCreators({
fetchAlbums : AlbumListActions.fetchAlbums
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(AlbumList);
As you can see, I am implementing touch event on the list item. But it is not triggering at all when I click on the card on Simulator. Why? How can I fix it?
You should wrap your content in component like this:
<TouchableWithoutFeedback>
<View>
<Your components...>
</View>
</TouchableWithoutFeedback>
TouchableWithoutFeedback always needs to have child View component. So a component that composes a View isn't enough.
So instead of
<TouchableWithoutFeedback onPressIn={...} onPressOut={...} onPress={...}>
<MyCustomComponent />
</TouchableWithoutFeedback>
use:
<TouchableWithoutFeedback onPressIn={...} onPressOut={...} onPress={...}>
<View>
<MyCustomComponent />
</View>
</TouchableWithoutFeedback>
See the github issue for more info
Can be used with <TouchableOpacity activeOpacity={1.0}> </TouchableOpacity>
For those who struggle with this issue in react-native 0.64, and wrapping it in just a View doesn't work, try this:
<TouchableWithoutFeedback onPress={onPress}>
<View pointerEvents="none">
<Text>Text</Text>
</View>
</TouchableWithoutFeedback>
In my case i accidentally imported TouchableWithoutFeedback from react-native-web instead of react-native. After importing from react-native everything worked as expected.
In more recent React Native versions, just use Pressable instead:
https://reactnative.dev/docs/pressable
In my case, there was a shadow underneath, which caused instability. What I did to solve it was quite simple: zIndex: 65000
<View style={{ zIndex: 65000 }}>
<TouchableWithoutFeedback onPressIn={() => {}>
<View>
</View>
</TouchableWithoutFeedback>
</View>