React native open modal into a modal - javascript

How to open modal from the modal box in react native.
ex: I have one form in modal and one color picker field into the form so how can I open color picker to another modal.
Name: XYZ
Age: 21
Color: A (This is the color picker. When I click the text open color picker in modal)

Render the nested color modal inside the outer modal as content, the visibility of nested modal will depend on the click/press over the text. Consider the sample code below, for demonstration purpose, I have used react-native-color-picker library, you can use any other library as well.
Sample
Initializing state variable, outerModalVisible controls the visibility of outer modal and nestedModalVisible controls the visibility of nested modal. In the sample code, outerModalVisible value remain true but you can change it as per your requirement.
state={
outerModalVisible: true,
nestedModalVisible: false,
selected_color: '#fff'
}
In render method, defining the outer model and nested modal
<View>
<Modal visible={this.state.outerModalVisible} >
<View style={styles.container}>
<Text>Name: XYZ</Text>
<Text>Age: 21</Text>
<TouchableOpacity onPress={() =>{
this.setState({
nestedModalVisible: true
});
}}>
<Text>Color picker: {this.state.selected_color}</Text>
</TouchableOpacity>
<Modal
visible={this.state.nestedModalVisible}
>
<View style={styles.container}>
<ColorPicker
onColorSelected={color => {
this.setState({
selected_color: color,
nestedModalVisible: false
});
}}
style={{flex: 1}}
/>
<Button title="click to close" onPress={() =>{
this.setState({
nestedModalVisible: false
});
}} />
</View>
</Modal>
</View>
</Modal>
</View>
You may define the content of nested model in a separate component, I just keep it simple and defined it inside a single component.
Hope this will help!

Related

How to check if press outside a component in react-native?

I did a custom select but I have the problem to close it if I press outside the select or options. basically the "button" is a TouchableOpacity and when I click on it there appears the list of options. But now I can close it only by choosing one option or clicking back on the select button. Is there a way to check whether I click outside the TouchableOpacity or not? In simple react you can give an Id to the component and check it onClick event to see what you have clicked. Or you can use react's useRef hook which doesn't seem to work with react-native. I have this code (simplified):
const [isOpen, setIsOpen] = useState(false)
const toggle = () => { setIsOpen(!isOpen)}
//...
return (<View>
<TouchableOpacity onPress={toggle}>
<Text>Open select</Text>
</TouchableOpacity>
<View>
{isOpen && options.map(({value, label}) => <View key={value} onPress={toggle}>{label}</View>)}
</View>
</View>)
As you can see you can call toggle only if you press the select button or an option. I want to call setIsOpen(false) when I click outside the TouchableOpacity box.
Is there a way or library to do it?
First of all correct usage for toggle function is
setIsOpen(prevIsOpen => !prevIsOpen);
And regarding your question. Just wrap all screen into touchable component without any feedback.
const close = () => isOpen && setIsOpen(false);
return (
<TouchableWithoutFeedback onPress={close} style={{ flex: 1 }}>
<View>
<TouchableOpacity onPress={toggle}>
<Text>Open select</Text>
</TouchableOpacity>
<View>
{isOpen && options.map(({value, label}) => <View key={value} onPress={toggle}>{label}</View>)}
</View>
</View>
</TouchableWithoutFeedback>
);
you can use TouchableWithoutFeedback

How can I set focus on the first TouchableHighlight component (or another one, given by ref for instance) inside the modal when modal is opened?

How can I set focus to the first (or any given) TouchableHighlight component inside the modal when it's opened?
I'm using D-pad/kayboard/TV Remote
Let's use the fragment of the react-native documentation modal example:
<View style={{marginTop: 22}}>
<Modal
animationType="slide"
transparent={false}
visible={this.state.modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}>
<View style={{marginTop: 22}}>
<View>
<TouchableHighlight>
<Text>Button 1</Text>
</TouchableHighlight>
<TouchableHighlight>
<Text>Button 2</Text>
</TouchableHighlight>
<TouchableHighlight
onPress={() => {
this.setModalVisible(!this.state.modalVisible);
}}>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
<TouchableHighlight
onPress={() => {
this.setModalVisible(true);
}}>
<Text>Show Modal</Text>
</TouchableHighlight>
</View>
Navigation with D-pad works, but when the modal is opened, the first TouchableHighlight (button 1) is not focused, focus remains on the "Show Modal" button
Or, How can I set focus on the "Button 2" TouchableHighlight programatically?
TextInput for example has autoFocus, but TouchableHighlight no, if we are using only Touchable components inside a modal, I don;t know how to autoFocus them, or set implicitely it
Best Regards
Add hasTVPreferredFocus: true to the TouchableHighlight you want focused.
It is implemented on Android even though the documentation states that hasTVPreferredFocus is iOS-only.
Programmatically you can also force the focus by calling:
yourElementRef.setNativeProps({ hasTVPreferredFocus: true })
#mtkopone . is there any way to focus View component, i have a list which is inside View and i want to do some function calls when user focus on that View (onFocus,onBlur)
initialRef.current.setNativeProps({hasTVPreferredFocus: true});

Detox - Testing visibility of modal in react native

We are using detox to write E2E testing of a react native app where we have a case which needs to test if a modal appears after a button tap.
But detox was not able to identify modal with the given testID thought the modal opens as expected. Is there a different way test modal in reactnative using detox?
Below is the modal JSX
<Modal
testID="loadingModal"
animationType="none"
transparent
visible={loading}
onRequestClose={() => {}}
>
<View style={styles.modalContainer}>
<View style={styles.loginModal}>
<ActivityIndicator
animating
size="large"
color="#00e0ff"
/>
<Text style={styles.login}>Logging in...</Text>
</View>
</View>
</Modal>
And below is the code to test the visibility of modal
it('should have welcome screen', async () => {
....
await element(by.text('CONTINUE')).tap();
await waitFor(element(by.id('loadingModal'))).toBeVisible().withTimeout(5000);
await expect(element(by.id('loadingModal'))).toBeVisible(); // this always fails
});
React Native's Modal component creates a view controller which manages the rendering of child views on the native level. Unfortunately, it doesn't pass down testID, so the best way I've found is to wrap the contents of the modal in a <View>, and pass the testID prop to that component. In your case, you could do:
<Modal
animationType="none"
transparent
visible={loading}
onRequestClose={() => {}}
>
<View
style={styles.modalContainer}
testID="loadingModal" // Just move the testID to this element
>
<View style={styles.loginModal}>
<ActivityIndicator
animating
size="large"
color="#00e0ff"
/>
<Text style={styles.login}>Logging in...</Text>
</View>
</View>

Can't pass details from FlatList items to Modal

I have FlatList and I want to open each FlatList Item in Modal to see details. In this example I am trying to click on {rowData.data.display_name} to see {rowData.data.display_name} inside of Modal. I get data from API and when I open Modal it's not the same item that I clicked on. I am not sure how would I set up item id to see the same item inside of Modal? Any help would be appreciated!
Here is my code:
<FlatList
data={this.state._data}
renderItem={({item: rowData}) => {
return (
<View style={styles.container}>
<TouchableOpacity
onPress={this.openModal}>
<Text style={styles.title}>
{rowData.data.display_name}
</Text>
</TouchableOpacity>
<Modal
style={styles.modal}
ref={(modal) => this.modal = modal}
coverScreen={true}
swipeToClose={this.state.swipeToClose}
onClosed={this.onClose}
onOpened={this.onOpen}
onClosingState={this.onClosingState}>
<Text style={styles.text}>
{rowData.data.display_name}
</Text>
</Modal>
</View>
);
}}
keyExtractor={(item, index) => index}
/>
In my opinion you should put the Modal outside the FlatList. The way you codded it you're overwritting the "this.modal" every time a new Item from the list is created.
Then you would just accept changing the text of the Modal (probably via state) on your openModal function inside the Modal component, and on your renderItem, change the onPress to pass the infos from the container you want the modal to show.
onPress={()=> this.openModal(rowData.data)}>

Bring View on top of Modal using zIndex style with React-Native

zIndex has been introduced recently to React-Native to change the position of a View in the stack of layers.
Although, I'm not able to bring a View on top of a Modal component.
My code looks like this:
render() {
return (
<View>
<Modal visible>
{props.children}
</Modal>
<View style={{ zIndex: 1000 }}>
<Text>Loading...</Text>
</View>
</View>
);
}
I guess I could stop using <Modal> and create a regular animated <View> that would behave like the Modal, but I'd rather find another solution.
Any idea?
No amount of zIndex manipulation will bring something above a react-native Modal, unfortunately. The Modal component is a native view that sits on top of the rest of your react-native application. The only way to put something above it is to put something in the modal itself, or alternately to use a js only implementation of a Modal.
Incidentally, the react-native-community version of modal is also built on the react-native modal, so would have the same issue. There's a discussion about different js implementation here:
https://github.com/react-native-community/react-native-modal/issues/145
Not possible with modal. As the modal should always shown regardless of whatever the zIndex is given to it and other components in the screen
It will always shown you unless you make visible=false
To implement what you want. You could use a absolutely positioned view with some zIndex trick to move this view back and front.
render() {
return (
<View>
<View style={{position:'absolute',top:0,bottom:0,left:0,right:0,zIndex:visible?-1:2}}>
{props.children}
</View>
<View style={{ zIndex: 1 }}>
<Text>Loading...</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
modal: {
backgroundColor: 'white',
margin: 0, // This is the important style you need to set
alignItems: undefined,
justifyContent: undefined,
}
<Modal isVisible={true} style={styles.modal}>
<View style={{ flex: 1 }}>
<Text>Hello!</Text>
</View>
</Modal>
I was struggling with the same problem and finally, I solved it. here's what I found:
First, the problem:
the problem is the modal appears on top of all screen layers which means it will show on all app elements, so if you open two modals at the same time the app will make it all on each other cuz the app don't know what the modals order it should be, for that reason, you see your modal behind the other one.
Second, the solution:
to make the second modal opens on the first modal you need to arrange the order of the modals opening, which means you open the parent modal then open the child modal.
and for doing that I'm using promise which allow me to do that like this steps example:-
1 - create promise
const [isOpen, setOpen] = useState({ backDrop: false, modal: false }); // modals open state
function openDialog() {
return new Promise((resolve) => {
setOpen((previous) => ({ ...previous, backDrop: props.isOpen }))
resolve()
})
}
2 - I use useEffect() to trigger modal opening props and then invoke an async function which will await the openDialog() function at the first step above and then will set open state after the first function invoked.
useEffect(() => {
(async function () {
await openDialog()
setOpen((previous) => ({ ...previous, modal: props.isOpen }))
}())
}, [props.isOpen])
this how you can control opening the modals pre-arranged and on top of each other
You have to change the z-index of the modal not the one of the view (and a z-index of value 1 would suffice):
render() {
return (
<View>
<Modal visible style={{ zIndex: 1 }}>
{props.children}
</Modal>
<View>
<Text>Loading...</Text>
</View>
</View>
);
}
An element with a larger z-index generally covers an element with a lower one (MDN docs).
EDIT:
Another solution is to change the order of the elements:
render() {
return (
<View>
<View>
<Text>Loading...</Text>
</View>
<Modal visible>
{props.children}
</Modal>
</View>
);
}
With this solution you don't need z-index because the modal is already on top of the view.

Categories