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

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.

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

React Native FlatList refreshing not working

i got an problem with the refreshing on pull function. The FlatList renders fine, but pull to refresh is not working. This is my current sourcecode:
return (
<View style={GlobalStyles.flex1}>
<FlatList
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={() => {
console.log("onRefresh loadVocable");
loadVocables();
}}
/>
}
data={vocables}
keyExtractor={vocable => vocable.id}
onEndReached={() => {
if (!isRefreshing && !endReached) {
loadVocables();
}
}}
renderItem={vocable => (
<TouchableOpacity
onPress={() => {
props.navigation.navigate({ routeName: "editVocable", params: { vocable: vocable.item } });
}}
onLongPress={() => {
handleLongPress(vocable.item.id);
}}>
<Card style={styles.cardStyle}>
<View style={styles.part1}>
<Text style={styles.vocableText}>{vocable.item.wordENG}</Text>
<Text style={styles.vocableText}>{vocable.item.wordDE}</Text>
</View>
<View style={styles.part2}>
<Ionicons name={vocable.item.known ? "md-checkmark-circle" : "md-close-circle"} size={23} color={vocable.item.known ? Colors.success : Colors.danger} />
</View>
</Card>
</TouchableOpacity>
)}
/>
</View>
);
In the official docs is an example that says contentContainerStyle needs to be flex: 1 to know the height, that makes sence to me, so when i set contentContainerStyle with flex 1, refresh on pull works fine, but then i can't scroll anymore in the Flatlist and everthing get very tight, so the style also change then. Does anyone know why this happen?
The first picture is with "contentContainerStyle={{flex: 1}}" and the last one is without contentContainerStyle.
The answer was so easy, I compared a new project (there worked my code) to the one where the problem was and after 5 days I found the little error:
My import was wrong!
I imported FlatList like this:
import { FlatList } from "react-native-gesture-handler";
But it needs to get imported from React-Native so like this:
import { FlatList } from "react-native";
Thanks to #The1993, without the hint to compare the projects, maybe I would stuck forever on this error :D In the future I will compare working files to find any error!
contentContainerStyle is used to style inner content e.g items alignments, padding, etc
style is used to align its height and relations
You can replace style={{flex: 1}} instead of contentContainerStyle or wrap the parent element with flex: 1

React Native: Make a view take the whole screen when other view is not present

This may seem like a basic question, but I can't seem to figure it out or properly word a search on this.
I have a View with two other Views in it, one of which sometimes is hidden. So the component looks something like this:
function MyComponent (props) {
return (
<View style={{ flex: 1 }}>
{
props.showButtonView
? (
<View style={{ flex: ??? }}>
<Button title='do something' onPress={() => console.warn('doSomethign)} />
</View>
)
: null
}
<View style={{ flex: ?? }}>
<Stuff/>
</View>
</View>
)
}
Now, what I am trying to do is have the Stuff component cover the entire screen whenever the Button is not present. However, if the props.showButtonView is true, and we do want to see the view with the Button I only want need to see the button on the top, and then the rest is
whatever is in the Stuff component. How do I get this done? Is it based on the flex numbers?
Also, you may be wondering why I need to separate these two into separate Views in the first place, the reason for that is because there are other unrelated things in the Stuff component that cover the button and don't allow me to click it. Anyway, long story short, the separation of the two by View is mandatory for this case.
flex should be 1 for both the places.
Flex:1 Indicates that it will takes the whole space available after if. So, it doesn't really matters when a button is placed there or not. Whenprops.showButtonView is true then the button is at the top and rest your stuff is placed after that.
Otherwise,
When props.showButtonView is false then button render code will not be executed and then stuff view will be all over your
Try below code when props.showButtonView === true
function MyComponent (props) {
return (
<View style={{ flex: 1 }}>
{
props.showButtonView
? (
<View style={{ width: '100%' }}>
<Button title='do something' onPress={() => console.warn('doSomethign)} />
</View>
)
: null
}
<View style={{ flex: 1 }}>
<Stuff/>
</View>
</View>
)
}

React native open modal into a modal

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!

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>

Categories